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閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容