React Native: Redux 工程化實(shí)踐

Redux 中文文檔 在此. 如果想找具體可操作的案例, 文檔里面都有. 文末有彩蛋.

為什么會(huì)有 Redux ?


在 iOS 中, 隨著項(xiàng)目迭代, 功能越來越復(fù)雜, 如果還是采用 MVC 架構(gòu), 由于 Controller 內(nèi)部的職責(zé)太多, 而導(dǎo)致代碼塊耦合嚴(yán)重, 不利于測試和維護(hù), 由此, MVVM 應(yīng)運(yùn)而生.

MVVM 架構(gòu)中, 通過將表現(xiàn)邏輯和交互邏輯移到 view-model 中, 借助 RxSwift 等響應(yīng)式編程的框架, controller 監(jiān)聽 view-model 中的 view state 的變更, 而做出對(duì)應(yīng)的操作, 比如修改 view.

協(xié)調(diào)器持有對(duì) model 層的引用, 并且了解 view controller 樹的結(jié)構(gòu), 這樣, 它能夠?yàn)槊總€(gè)場景的 view-model 提供所需要的 model 對(duì)象. 如果不增加協(xié)調(diào)器, 那么 view controller 間就會(huì)有耦合.
實(shí)際項(xiàng)目中是否引入?yún)f(xié)調(diào)器, 得看具體情況. 如果是針對(duì)不是那么復(fù)雜的功能做重構(gòu), 太復(fù)雜的架構(gòu)反而是畫蛇添足.


回到 React Native 項(xiàng)目, 如果 JavaScript 單頁應(yīng)用功能越來越復(fù)雜, 我們同樣要處理功能模塊解耦, 更細(xì)一點(diǎn), 處理各種變化的 state. 這些 state 可能包括服務(wù)器響應(yīng)數(shù)據(jù), 緩存數(shù)據(jù), 也包括 UI 狀態(tài), 如被選中的標(biāo)簽, 是否顯示加載動(dòng)效或者分頁器等等.

所以我們選擇了 Redux.

Redux 是什么 ?


ReduxJavaScrip 狀態(tài)容器, 提供可預(yù)測化的 state 管理.

Redux 中, 這些 state , 也可以稱之為 model 數(shù)據(jù).
通過 action(交互邏輯, 顯示邏輯), 更改不同的 state, 最后顯示在界面上.

在下面代碼中, POPULAR_REFRESHPOPULAR_REFRESH_SUCCESS 代表兩種 action , 對(duì)于不同的 action, 內(nèi)部需要傳遞的 state 數(shù)據(jù)也不同, 最終傳遞到 JavaScript 頁面, 映射到 props 中, 做最后的處理.

case Types.POPULAR_REFRESH:   //下拉刷新中
    return {
        ...state, 
        [action.storeName]: {    // storeName 是類似于 java, ios等這些tab, 它是動(dòng)態(tài)的
            ...state[action.storeName],
            refreshState: 1,
        }
    };
case Types.POPULAR_REFRESH_SUCCESS:   //下拉刷新成功
    return {
        ...state, 
        [action.storeName]: {
            ...state[action.storeName],
            items: action.items, //原始數(shù)據(jù)
            projectModels: action.projectModels,  // 此次要展示的數(shù)據(jù)
            refreshState: 0,    // 默認(rèn)
            pageIndex: action.pageIndex
        }
    };

Redux 的工作流程

Redux 的工作流程

    1. 用戶操作View, 通過dispatch方法, 發(fā)出 Action.
    • Action 可以是網(wǎng)絡(luò)請(qǐng)求, 交互邏輯等.
    1. Store 自動(dòng)調(diào)用 Reducer, 并且傳入兩個(gè)參數(shù)(當(dāng)前 State 和收到的 Action ), Reducer 會(huì)返回新的 State.
    • 如果有 Middleware, Store 會(huì)將當(dāng)前 State 和收到的 Action 傳遞給 Middleware, Middleware 會(huì)調(diào)用 Reducer 然后返回新的 State.
    1. State 一旦有變化, Store 就會(huì)調(diào)用監(jiān)聽函數(shù), 更新 View.

在整個(gè)流程中, 數(shù)據(jù)都是單向流動(dòng)的.

Redux 的三原則
  1. Redux 應(yīng)用中所有的 state 都以一個(gè)對(duì)象樹的形式存儲(chǔ)在一個(gè) 單一store 中.
  2. state只讀 的: 唯一改變 state 的辦法是觸發(fā) action, action 是一個(gè)描述發(fā)生什么的對(duì)象.
  3. 使用純函數(shù)來執(zhí)行修改: 為了描述 action 如何改變 state 樹, 你需要編寫 reducers.
    reducer 是形式為 (state, action) => state 的純函數(shù). 根據(jù) action 修改 state, 將其轉(zhuǎn)變?yōu)橄乱粋€(gè) state.

Redux 在 React Native 中的應(yīng)用


準(zhǔn)備工作

根據(jù)需要, 安裝以下組件.

  • redux(必選).
  • react-redux(必選): redux 作者開發(fā)的一個(gè)在 React 上使用的 redux 庫.
  • redux-devtools(可選): Redux 開發(fā)者工具, 支持熱加載, action 重放, 自定義 UI 等功能.
  • redux-thunk(可選): 實(shí)現(xiàn) action 異步的 middleware.
  • redux-persist(可選): 支持 store 本地持久化.
  • redux-observable(可選): 實(shí)現(xiàn)可取消的 action.

安裝方式

yarn add redux react-redux redux-devtools

react-redux 介紹

react-reduxRedux 官方提供的 React 綁定庫.

有幾個(gè)位置需要注意:

  • <Provider> 組件: 這個(gè)組件需要包裹在整個(gè)組件樹的最外層(根組件). 讓所有的子組件都能使用 connect() 方法綁定 store.
  • connect(): 這是 react-redux 提供的一個(gè)方法, 如果一個(gè)組件想要響應(yīng)狀態(tài)的變化, 就需要把自己作為參數(shù)傳給 connect() 的結(jié)果, connect() 方法會(huì)處理與 store 綁定的細(xì)節(jié), 并通過 selector 確定該綁定 store 的哪一部分的數(shù)據(jù).
  • selector: 這是我們自定義的函數(shù), 這個(gè)函數(shù)聲明了你的組件需要整個(gè) store 中的哪一部份數(shù)據(jù)作為自己的 props.
  • dispatch: 每當(dāng)需要改變應(yīng)用中的 state, 都需要 dispatch 一個(gè) action.

使用步驟

1. 創(chuàng)建 action

定義 action 類型

// 各種 action 類型
export const THEME_CHANGE = 'THEME_CHANGE'
export const POPULAR_REFRESH = 'POPULAR_REFRESH'

// 各種 action 類型
export default {
    THEME_CHANGE: "THEME_CHANGE", 
    POPULAR_REFRESH: "POPULAR_REFRESH"
}

創(chuàng)建 action 函數(shù)

import Types from '../types';

export function onThemeChange(theme) {
    // 同步 action
    // return {   
    //     type: Types.THEME_CHANGE,
    //     theme: theme,
    // }

    // 異步 action  需要引入 'redux-thunk'
    return dispatch => {
        dispatch({
            type: Types.THEME_CHANGE,
            theme: theme,
        })
    };
}

注意:

  • 這里我們傳入了一個(gè)參數(shù) theme, 是我們將要修改的主題樣式.
  • action 既可以同步實(shí)現(xiàn), 也可以異步實(shí)現(xiàn). 對(duì)于網(wǎng)絡(luò)請(qǐng)求, 數(shù)據(jù)庫加載等應(yīng)用場景, 我們必須使用異步 action,
  • 異步 action 可以理解為, 在 action 內(nèi)部進(jìn)行異步操作, 等操作返回后, 在 dispatch 一個(gè) action.
  • 為了使用異步 action, 我們需要引入 redux-thunk 庫. 將異步中間件添加到 store 中.
import thunk from 'redux-thunk'

const middlewares = [
    thunk,
    middleware2,
    middleware3,
];

export default createStore(reducers, applyMiddleware(...middlewares));
  • 默認(rèn)情況下, createStore() 所創(chuàng)建的 Redux store 沒有使用 middleware, 所以只支持同步數(shù)據(jù)流.

  • 我們可以使用 applyMiddleware() 來增強(qiáng) createStore(), 添加 thunk 這類中間件來實(shí)現(xiàn)異步 action.

  • redux-thunkredux-promise 這類支持異步的 moddleware 都包裝了 storedispatch() 方法. 因此我們可以 dispatch 一些除了 action 以外的內(nèi)容. 例如函數(shù)或者 Promise.

  • 注意: 當(dāng) middleware 鏈中的最后一個(gè) moddleware 開始 dispatch action 時(shí), 這個(gè) action 必須是一個(gè)普通對(duì)象.

2. 創(chuàng)建 reducers

reducer 是根據(jù) action 類型 修改 state, 將其轉(zhuǎn)變成下一個(gè) state. 這里面根據(jù)實(shí)際的需要, 定義了各種不同的 state 樹.

import Types from '../../action/types';

const defaultState = {
    theme: 'red'
}

export default function onAction(state=defaultState, action) {
    switch (action.type) {
        case Types.THEME_CHANGE:
            return {
                ...state,
                theme: action.theme,
            }
        default:
            return state;
    }
}

注意

  • reducer 是一個(gè)純函數(shù), 他僅僅用于返回下一個(gè) state, 為了保證 reducer 盡可能簡單, 我們不能在這里面改變 state, 只能在 action 創(chuàng)建函數(shù) 內(nèi)部做.
  • reducer 內(nèi)部也不要調(diào)用非純函數(shù), Date.now()Math.random() 這種.
  • 在默認(rèn)的情況下, 要返回舊的 state. 以應(yīng)對(duì)未知 action 的情況.
  • 對(duì)于獨(dú)立 page 的 reducer, 我們應(yīng)該針對(duì)各個(gè)頁面進(jìn)行拆分, 以免 action 太多. 導(dǎo)致不容易維護(hù). 拆分完我們需要合并進(jìn)行使用.
import {combineReducers} from 'redux';

import theme from './theme';
import popular from './popular';

// 合并 reducer
const index = combineReducers({
    themeReducer: theme,
    popularReducer: popular,
})

export default index;
  • combineReducers() 所做的只是生成一個(gè)函數(shù), 這個(gè)函數(shù)來調(diào)用你的一系列 reducer, 每個(gè) reducer 根據(jù)他們的 key 來篩選出 state 中的一部分?jǐn)?shù)據(jù)并處理, 然后這個(gè)生成的函數(shù)再將所有 reducer 的結(jié)果合并成一個(gè)大的對(duì)象. 如果combineReducers() 所包含的所有 reducers 都沒有更改 state, 那么就不會(huì)創(chuàng)建一個(gè)新的對(duì)象.

3. 使用 store

Store 是 存儲(chǔ) state 的容器.

  • 它會(huì)把兩個(gè)參數(shù)(當(dāng)前的 state 樹 和 action) 傳入 reducer
  • reducer 會(huì)把新的 state 返回給 store,
  • store 更新 state 到 view 中.

store 里有幾個(gè)方法

  • 提供 getState() 方法獲取 state.
  • 提供 dispatch(action) 方法更新 state.
  • 通過 subscribe(listener) 注冊(cè)監(jiān)聽器.
  • 通過 subscribe(listener) 返回的函數(shù), 注銷監(jiān)聽器.

配置 store

import {createStore, applyMiddleware} from 'redux';
import reducers from '../reducer/reducer';
import thunk from 'redux-thunk'

const logger = store => next => action => {
    if (typeof action === 'function') {
        console.log('dispatching a function')
    } else {
        console.log('dispatching', action)
    }
    console.log('nextState', store.getState());
};

const middlewares = [
    logger,  // 打印 state 信息
    thunk,   // 提供異步 action
];

// 在 store 中添加中間件, 配置 reducer
export default createStore(reducers, applyMiddleware(...middlewares));

使用 store, 我們首先要引入 react-redux 庫, 在 App 的根組件, 通過 <Provider/> 配置 store.

import {Provider} from 'react-redux';
import store from './js/store';

export default class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <AppNavigators />
      </Provider>
    )
  }
}

3. 在組件中應(yīng)用 Redux

訂閱 state, dispatch

import {connect} from 'react-redux';
import actions from '../action/index';

class HomePage extends Component {
  render() {
    return (
      <View style={styles.container}>
        <Button 
          title='改變主題'
          onPress={()=> {
            this.props.onThemeChangeProp('orange')
          }}
        />
      </View>
    )
  }
}

const mapStateToProps = state => ({
  themeProp: state.themeReducer.theme
});

const mapDispatchToProps = dispatch => ({
  onThemeChangeProp: theme => dispatch(actions.onThemeChange(theme)),
});

export default connect(mapStateToProps, mapDispatchToProps)(HomePage);

在上述代碼中, 我們訂閱了 store 中的 theme state 和 dispatch,

  • 我們通過 react-redux 提供的 connect() 方法, 將 store 中的目標(biāo) state, 和處理該 state 的 action 所在的 selector 傳入其中.
  • 并且將目標(biāo)組件傳入 connect() 的結(jié)果中, 使得目標(biāo)組件響應(yīng) state 的變化.
  • 這樣該組件就可以通過 props 取出對(duì)應(yīng)的state, 和操作 action.


react-navigation + Redux

如果你是通過 react-navigation 做 App 的基礎(chǔ)架構(gòu), Redux 也對(duì)其做了支持.
我們需要導(dǎo)入 react-navigation-redux-helpers 庫.

1. 配置 navigator

import {
    createAppContainer, createStackNavigator, createSwitchNavigator
}
from 'react-navigation';

import HomePage from '../page/HomePage';

import { connect } from "react-redux";
import {
    createReactNavigationReduxMiddleware, 
    createReduxContainer
}
from  "react-navigation-redux-helpers"

export const rootCom = 'HomePage';  // 設(shè)置根路由

const MainNavigator = createStackNavigator({
    HomePage: {
        screen: HomePage,
    }, 
}, {
    initialRouteName: rootCom
});

export const RootNavigator = createAppContainer(MainNavigator);

/**
 * 1. 初始化 react-navigation 與 redux 的中間件
 * 該方法的一個(gè)很大的作用是為 reduxifyNavigator 的 key 設(shè)置 actionSubscribers (行為訂閱者)
 */
export const middleware = createReactNavigationReduxMiddleware(
    state => state.nav,
);

/**
 * 2. 將導(dǎo)航器傳遞給 reduxifyNavigator 函數(shù)
 * 并返回一個(gè)將 navigation state 和 dispatch 函數(shù)作為 props 的新組件
 * 注意: 要在 createReactNavigationReduxMiddleware 之后執(zhí)行
 */
const AppWithNavigationState = createReduxContainer(RootNavigator);

/**
 * State 和 Props 的映射關(guān)系
 */
const mapStateToProps = state => ({
    state: state.nav  
})

/**
 * 連接 React 組件 與 Redux store
 */
export default connect(mapStateToProps)(AppWithNavigationState);

2. 配置 reducer

import {combineReducers} from 'redux';
import {createNavigationReducer} from 'react-navigation-redux-helpers'

import {RootNavigator} from '../navigator/AppNavigators';

import theme from './theme';
import popular from './popular';

// 1. 創(chuàng)建自己的 navigation reducer
const navReducer = createNavigationReducer(RootNavigator)

// 2. 合并 reducer
const index = combineReducers({
    nav: navReducer,
    themeReducer: theme,    // 子 reducer
    popularReducer: popular,// 子 reducer
})

export default index;

3. 使用 navigator

import {Provider} from 'react-redux';
import store from './js/store';

import AppNavigators from './js/navigator/AppNavigators';

export default class App extends Component {
  render() {
    return (
      <Provider store={store}>
        <AppNavigators />
      </Provider>
    )
  }
}

至此, Redux 的基本使用都已經(jīng)介紹完了.

Tips

  • Redux 應(yīng)用只有一個(gè) store, 當(dāng)需要拆分?jǐn)?shù)據(jù), 處理邏輯時(shí), 應(yīng)該使用 reducer 組合.
  • redux 有一個(gè)特點(diǎn), 狀態(tài)共享, 所有的狀態(tài)都放在一個(gè) store 中, 任何 component 都可以訂閱 store 中的 state 數(shù)據(jù).
  • 并不是所有的 state 都適合放在 store 中, 這樣會(huì)使 store 越來越大. 如果某個(gè) state 只被一個(gè)組件使用, 不存在狀態(tài)共享, 可以不放在 store 中.
  • 如果你的項(xiàng)目不追求極致的條理, 可以不使用 Redux, 就好像 iOS 中再大的項(xiàng)目, MVC 這種架構(gòu)都是可以應(yīng)對(duì), 并且有針對(duì)其架構(gòu)的方案, 而不是使用 MVVM, 新的架構(gòu)是有學(xué)習(xí)成本的.
  • 具體適不適合你自己的項(xiàng)目, 自己掂量.

enjoy :).

參考

如何理解 redux 的流程
react native redux 指南
官方中文文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容