dva之React Naitve中的战斗攻略
前言
从最早接触react native也快接近一年了,不多不少的也做了有3个项目了,但是技术好像没有什么提升诶(😀😀😁),其中很有感触的是在开发一个收入的项目的时候,做下来发现文件太多了,不好管理,根据问题查看代码很是膈应。不过还好的是最近接触到了一个叫dva的前端框架(听说支付宝前端团队开发的框架),dva是出自于守望先锋游戏的一个角色 => D.Va拥有一部强大的机甲,装备了各种高科技武器。同样dva框架呢是对redux+saga这种方式管理数据流的整合封装,目的很简单,让使用者更简单的,更方便的管理数据流。
dva的作用
从代码结构管理层面
以前的项目就是使用原生的redux管理的,当然还有处理异步操作的saga,所以针对一个业务点,代码会分布在很多文件中。
image.png下图,则是通过dva来管理的react native项目,action,reducer,saga都放在model模块,相对简洁很多。
image.png其中model的编写是dva的核心。
从代码编写繁琐程度
redux store 的创建,actionCreater的创建,中间件的配置,路由的初始化,Provider 的 store 的绑定,saga 的初始化,还要处理 reducer, component。
基于上面的这些问题,封装了 dva 。dva 是基于 redux 最佳实践 实现的 framework。
dva接入的课前辅导
简单画下我对Redux,Redux-sage 整个流程的理解。
-
Redux
image.png -
Redux-Saga
image.png
react native+dva+react-navigation 的一个demo
项目结构如下图
image.png接下来我们就从零搭建,一定要动手去敲哦!!!👻
通过dva初始化根页面
react native 的默认初始化方式, 第二个参数是Component类型
AppRegistry.registerComponent('XXXApp', () => XXXAppComponent)
but,right now
index.ios.js
import {
AppRegistry
} from 'react-native'
import app from './src'
AppRegistry.registerComponent('ReduxTest', app)
这个app是个什么东东呢,先卖个关子😈
app.js
import React from 'react'
import dva, { connect } from 'dva/mobile'
import { registerModels } from './models'
import Router from './routes'
// 1. Initialize
const app = dva()
// 2. Model
registerModels(app)
// 3. Router
app.router(() => <Router />)
// 4. Start
export default () => {
return app.start()
}
其中步骤2中的注册model,可以先不用care,重点放在后两个步骤,Router是个什么东西呢,你可以简单的理解为RootComponent,我们一般开发react native的RootComponent即为TabNavigator,StackNavigator,该demo以StackNavigator为根页面,所以呢,我们就简单的将其导出为Router,然后注册到dva中,app.start()
将会启动应用,并返回一个Component。这也很好的解释了AppRegistry中注册app.start()
返回的Component。
Router ???
router.js
import {
StackNavigator,
addNavigationHelpers
} from 'react-navigation'
import React, { Component } from 'react'
import { BackHandler, Animated, Easing } from 'react-native'
import { connect } from 'dva'
import Login from '../pages/Login'
import Profile from '../pages/Profile'
const AppNavigator = StackNavigator(
{
Login: {screen: Login},
Profile: {screen: Profile}
},
{
navigationOptions: {
gesturesEnabled: true,
},
}
)
@connect(({ router }) => ({ router }))
export default class Router extends Component {
render() {
const { dispatch, router } = this.props
const navigation = addNavigationHelpers({ dispatch, state: router })
return <AppNavigator navigation={navigation} />
}
}
export function routerReducer(state, action = {}) {
return AppNavigator.router.getStateForAction(action, state)
}
Router主要是简单定义了下StackNavigator中的存放的Component,默认第一个为RootComponent 即Login。需要解释一下的是@connect(({ router }) => ({ router }))
这是es7的语法,有兴趣可以google下,这里我只把router数据给传进来,addNavigationHelpers({ dispatch, state: router })
是将会在执行navigation.goBack()
,navigation.navigate()
的同时执行对应的dispatch,更新router数据。
export function routerReducer
这个外部接口, 提供外部获取路由信息。
Login.js
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
TouchableOpacity,
Text,
View
} from 'react-native'
import { connect } from 'dva'
import {
NavigationActions
} from 'react-navigation'
@connect(
appNS => ({ ...appNS }),
{
increase: () => (({ type: 'appNS/add' })),
login: () => (({ type: 'appNS/login' }))
}
)
export default class Login extends Component {
static navigationOptions = {
title: '登录页',
}
goLogin() {
this.props.login()
}
render() {
return (
<View style={styles.container}>
<TouchableOpacity style={styles.loginButton} onPress={() => this.goLogin()}>
<Text style={styles.loginLabel}>登录</Text>
</TouchableOpacity>
</View>
);
}
}
like this
image.png其中点击登录按钮会触发dispatch({ type: 'appNS/login' })。接下来我们就来看看重中之中针对这个页面的model编写。
models/app.js
import { NavigationActions } from '../tools'
import { createAction } from '../tools'
import { get, post } from '../tools/fetch'
export default {
namespace: 'appNS',
state: {
isLogin: false,
userName: '路人甲',
loginFailedReason: 'no reason',
count: 0
},
reducers: {
add(state, { payload }) {
return {
...state,
count: (state.count + 1)
}
},
loginSuccessed(state, { payload }) {
return {
...state,
isLogin: true,
userName: payload.userName
}
},
loginFailed(state, { payload }) {
return {
...state,
isLogin: false,
loginFailedReason: payload.loginFailedReason
}
}
},
effects: {
*login(payload, { put, call }) {
// yield put({ type: 'loginSuccessed', {'name': 'yellow'} })
// yield put(createAction('loginSuccessed')({'name': 'yellow'}))
try {
const res = yield call(() => get('https://httpbin.org/get'))
if (res.url) {
yield put(createAction('loginSuccessed')({'userName': 'yellow'}))
yield put(NavigationActions.navigate({ routeName: 'Profile'}))
} else {
yield put(createAction('loginFailed')({'loginFailedReason': '账号密码错误'}))
}
} catch (e) {
console.log(e);
}
}
}
}
首先,简单说明下model 就是一个大的json对象,其中有几个重要的key。namespace,当你connect一个Component就是通过这个值来连接的,以及跨model调用action,ex:
put({type:'namespace/xxaction'})
,state放置一些初始化或是需要维护的数据。reducers里就放一些action对应的纯函数,修改state数据源。effects存放一些网络请求,I/O操作的有副作用的方法,其中会调用reducer的方法,从而改变数据源。
帮助大家理一下流程
page/this.props.login() ——》effects/*login ——》success? reducer/loginSuccessedAction——》state/isLogin: true
effects中有两个比较常用的辅助函数
put
,call
,put函数调用一个action,call用于调用异步逻辑,支持 promise
如何在effects中进行页面的跳转呢?
以前我遇到这个问题也很头疼,就用了一个很暴力的方法,用global全局对象来保存Navigator,然后来进行操作。但是react-navigation这个第三方组件,既支持UI层面的页面切换,也支持对redux的接入(路由信息的获取和修改,修改也会影响到UI)
models/router.js
import { createAction, NavigationActions } from '../tools'
import { routerReducer } from '../routes'
const watcher = { type: 'watcher' }
const actions = [
NavigationActions.BACK,
NavigationActions.INIT,
NavigationActions.NAVIGATE,
NavigationActions.RESET,
NavigationActions.SET_PARAMS,
NavigationActions.URI,
]
export default {
namespace: 'router',
state: {
...routerReducer(),
},
reducers: {
apply(state, { payload: action }) {
return routerReducer(state, action)
},
},
effects: {
watch: [
function*({ take, call, put }) {
while (true) {
const payload = yield take(actions)
yield put(createAction('apply')(payload))
if (payload.type === 'Navigation/NAVIGATE') {
console.log('11111',payload);
}
}
}, watcher]
},
}
22.gif其实就做了两件事,通过之前的Router组件中提供的获取路由信息初始化到state中,effects中监听NavigationActions,然后调用apply来更新路由信息,最后又因为我们将路由信息链接到Router组件,所以就会有UI页面的切换。
二维码地址
image.png
总结来说,dva虽然屏蔽了redux和saga的一些细节,但你要真正运用到项目中,还是需要恶补下这方面的知识,前端框架变化莫测,如何拥有一个自学的方法是很关键的,以及学习的及时反馈,对于新人来说一剂强力的助推器。