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

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

22.gif

完整demo

二维码地址


image.png

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

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

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容