React Native: Redux 工程化实践

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

为什么会有 Redux ?


在 iOS 中, 随着项目迭代, 功能越来越复杂, 如果还是采用 MVC 架构, 由于 Controller 内部的职责太多, 而导致代码块耦合严重, 不利于测试和维护, 由此, MVVM 应运而生.

MVVM 架构中, 通过将表现逻辑和交互逻辑移到 view-model 中, 借助 RxSwift 等响应式编程的框架, controller 监听 view-model 中的 view state 的变更, 而做出对应的操作, 比如修改 view.

1208639-38f13f0e0fb89b5c.png

协调器持有对 model 层的引用, 并且了解 view controller 树的结构, 这样, 它能够为每个场景的 view-model 提供所需要的 model 对象. 如果不增加协调器, 那么 view controller 间就会有耦合.
实际项目中是否引入协调器, 得看具体情况. 如果是针对不是那么复杂的功能做重构, 太复杂的架构反而是画蛇添足.


回到 React Native 项目, 如果 JavaScript 单页应用功能越来越复杂, 我们同样要处理功能模块解耦, 更细一点, 处理各种变化的 state. 这些 state 可能包括服务器响应数据, 缓存数据, 也包括 UI 状态, 如被选中的标签, 是否显示加载动效或者分页器等等.

所以我们选择了 Redux.

Redux 是什么 ?


ReduxJavaScrip 状态容器, 提供可预测化的 state 管理.

Redux 中, 这些 state , 也可以称之为 model 数据.
通过 action(交互逻辑, 显示逻辑), 更改不同的 state, 最后显示在界面上.

在下面代码中, POPULAR_REFRESHPOPULAR_REFRESH_SUCCESS 代表两种 action , 对于不同的 action, 内部需要传递的 state 数据也不同, 最终传递到 JavaScript 页面, 映射到 props 中, 做最后的处理.

case Types.POPULAR_REFRESH:   //下拉刷新中
    return {
        ...state, 
        [action.storeName]: {    // storeName 是类似于 java, ios等这些tab, 它是动态的
            ...state[action.storeName],
            refreshState: 1,
        }
    };
case Types.POPULAR_REFRESH_SUCCESS:   //下拉刷新成功
    return {
        ...state, 
        [action.storeName]: {
            ...state[action.storeName],
            items: action.items, //原始数据
            projectModels: action.projectModels,  // 此次要展示的数据
            refreshState: 0,    // 默认
            pageIndex: action.pageIndex
        }
    };

Redux 的工作流程

1208639-017cd0fd95caf8ad.jpg
Redux 的工作流程

    1. 用户操作View, 通过dispatch方法, 发出 Action.
    • Action 可以是网络请求, 交互逻辑等.
    1. Store 自动调用 Reducer, 并且传入两个参数(当前 State 和收到的 Action ), Reducer 会返回新的 State.
    • 如果有 Middleware, Store 会将当前 State 和收到的 Action 传递给 Middleware, Middleware 会调用 Reducer 然后返回新的 State.
    1. State 一旦有变化, Store 就会调用监听函数, 更新 View.

在整个流程中, 数据都是单向流动的.

Redux 的三原则
  1. Redux 应用中所有的 state 都以一个对象树的形式存储在一个 单一store 中.
  2. state只读 的: 唯一改变 state 的办法是触发 action, action 是一个描述发生什么的对象.
  3. 使用纯函数来执行修改: 为了描述 action 如何改变 state 树, 你需要编写 reducers.
    reducer 是形式为 (state, action) => state 的纯函数. 根据 action 修改 state, 将其转变为下一个 state.

Redux 在 React Native 中的应用


准备工作

根据需要, 安装以下组件.

  • redux(必选).
  • react-redux(必选): redux 作者开发的一个在 React 上使用的 redux 库.
  • redux-devtools(可选): Redux 开发者工具, 支持热加载, action 重放, 自定义 UI 等功能.
  • redux-thunk(可选): 实现 action 异步的 middleware.
  • redux-persist(可选): 支持 store 本地持久化.
  • redux-observable(可选): 实现可取消的 action.

安装方式

yarn add redux react-redux redux-devtools

react-redux 介绍

react-reduxRedux 官方提供的 React 绑定库.

有几个位置需要注意:

  • <Provider> 组件: 这个组件需要包裹在整个组件树的最外层(根组件). 让所有的子组件都能使用 connect() 方法绑定 store.
  • connect(): 这是 react-redux 提供的一个方法, 如果一个组件想要响应状态的变化, 就需要把自己作为参数传给 connect() 的结果, connect() 方法会处理与 store 绑定的细节, 并通过 selector 确定该绑定 store 的哪一部分的数据.
  • selector: 这是我们自定义的函数, 这个函数声明了你的组件需要整个 store 中的哪一部份数据作为自己的 props.
  • dispatch: 每当需要改变应用中的 state, 都需要 dispatch 一个 action.

使用步骤

1. 创建 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"
}

创建 action 函数

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,
        })
    };
}

注意:

  • 这里我们传入了一个参数 theme, 是我们将要修改的主题样式.
  • action 既可以同步实现, 也可以异步实现. 对于网络请求, 数据库加载等应用场景, 我们必须使用异步 action,
  • 异步 action 可以理解为, 在 action 内部进行异步操作, 等操作返回后, 在 dispatch 一个 action.
  • 为了使用异步 action, 我们需要引入 redux-thunk 库. 将异步中间件添加到 store 中.
import thunk from 'redux-thunk'

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

export default createStore(reducers, applyMiddleware(...middlewares));
  • 默认情况下, createStore() 所创建的 Redux store 没有使用 middleware, 所以只支持同步数据流.

  • 我们可以使用 applyMiddleware() 来增强 createStore(), 添加 thunk 这类中间件来实现异步 action.

  • redux-thunkredux-promise 这类支持异步的 moddleware 都包装了 storedispatch() 方法. 因此我们可以 dispatch 一些除了 action 以外的内容. 例如函数或者 Promise.

  • 注意: 当 middleware 链中的最后一个 moddleware 开始 dispatch action 时, 这个 action 必须是一个普通对象.

2. 创建 reducers

reducer 是根据 action 类型 修改 state, 将其转变成下一个 state. 这里面根据实际的需要, 定义了各种不同的 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 是一个纯函数, 他仅仅用于返回下一个 state, 为了保证 reducer 尽可能简单, 我们不能在这里面改变 state, 只能在 action 创建函数 内部做.
  • reducer 内部也不要调用非纯函数, Date.now()Math.random() 这种.
  • 在默认的情况下, 要返回旧的 state. 以应对未知 action 的情况.
  • 对于独立 page 的 reducer, 我们应该针对各个页面进行拆分, 以免 action 太多. 导致不容易维护. 拆分完我们需要合并进行使用.
import {combineReducers} from 'redux';

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

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

export default index;
  • combineReducers() 所做的只是生成一个函数, 这个函数来调用你的一系列 reducer, 每个 reducer 根据他们的 key 来筛选出 state 中的一部分数据并处理, 然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象. 如果combineReducers() 所包含的所有 reducers 都没有更改 state, 那么就不会创建一个新的对象.

3. 使用 store

Store 是 存储 state 的容器.

  • 它会把两个参数(当前的 state 树 和 action) 传入 reducer
  • reducer 会把新的 state 返回给 store,
  • store 更新 state 到 view 中.

store 里有几个方法

  • 提供 getState() 方法获取 state.
  • 提供 dispatch(action) 方法更新 state.
  • 通过 subscribe(listener) 注册监听器.
  • 通过 subscribe(listener) 返回的函数, 注销监听器.

配置 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. 在组件中应用 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 中的目标 state, 和处理该 state 的 action 所在的 selector 传入其中.
  • 并且将目标组件传入 connect() 的结果中, 使得目标组件响应 state 的变化.
  • 这样该组件就可以通过 props 取出对应的state, 和操作 action.


react-navigation + Redux

如果你是通过 react-navigation 做 App 的基础架构, Redux 也对其做了支持.
我们需要导入 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';  // 设置根路由

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

export const RootNavigator = createAppContainer(MainNavigator);

/**
 * 1. 初始化 react-navigation 与 redux 的中间件
 * 该方法的一个很大的作用是为 reduxifyNavigator 的 key 设置 actionSubscribers (行为订阅者)
 */
export const middleware = createReactNavigationReduxMiddleware(
    state => state.nav,
);

/**
 * 2. 将导航器传递给 reduxifyNavigator 函数
 * 并返回一个将 navigation state 和 dispatch 函数作为 props 的新组件
 * 注意: 要在 createReactNavigationReduxMiddleware 之后执行
 */
const AppWithNavigationState = createReduxContainer(RootNavigator);

/**
 * State 和 Props 的映射关系
 */
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. 创建自己的 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 的基本使用都已经介绍完了.

Tips

  • Redux 应用只有一个 store, 当需要拆分数据, 处理逻辑时, 应该使用 reducer 组合.
  • redux 有一个特点, 状态共享, 所有的状态都放在一个 store 中, 任何 component 都可以订阅 store 中的 state 数据.
  • 并不是所有的 state 都适合放在 store 中, 这样会使 store 越来越大. 如果某个 state 只被一个组件使用, 不存在状态共享, 可以不放在 store 中.
  • 如果你的项目不追求极致的条理, 可以不使用 Redux, 就好像 iOS 中再大的项目, MVC 这种架构都是可以应对, 并且有针对其架构的方案, 而不是使用 MVVM, 新的架构是有学习成本的.
  • 具体适不适合你自己的项目, 自己掂量.

enjoy :).

参考

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

猜你喜欢

转载自blog.csdn.net/weixin_33739541/article/details/87109762