ReactNative首页投稿(暂停使用,暂停投稿)技术干货

dva之React Naitve中的战斗攻略

2017-06-22  本文已影响5381人  yellow超

前言

从最早接触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,Redux-sage 整个流程的理解。

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]
  },
}

其实就做了两件事,通过之前的Router组件中提供的获取路由信息初始化到state中,effects中监听NavigationActions,然后调用apply来更新路由信息,最后又因为我们将路由信息链接到Router组件,所以就会有UI页面的切换。

22.gif

完整demo

二维码地址


image.png

总结来说,dva虽然屏蔽了redux和saga的一些细节,但你要真正运用到项目中,还是需要恶补下这方面的知识,前端框架变化莫测,如何拥有一个自学的方法是很关键的,以及学习的及时反馈,对于新人来说一剂强力的助推器。

最后上一张我家猫咪生的小宝宝 嘻嘻

image.png
上一篇下一篇

猜你喜欢

热点阅读