Redux 中文文档 在此. 如果想找具体可操作的案例, 文档里面都有. 文末有彩蛋.
为什么会有 Redux ?
在 iOS 中, 随着项目迭代, 功能越来越复杂, 如果还是采用 MVC 架构, 由于 Controller
内部的职责太多, 而导致代码块耦合严重, 不利于测试和维护, 由此, MVVM 应运而生.
在 MVVM 架构中, 通过将表现逻辑和交互逻辑移到 view-model
中, 借助 RxSwift
等响应式编程的框架, controller
监听 view-model
中的 view state 的变更, 而做出对应的操作, 比如修改 view.
![1208639-38f13f0e0fb89b5c.png](https://upload-images.jianshu.io/upload_images/1208639-38f13f0e0fb89b5c.png)
协调器持有对 model
层的引用, 并且了解 view controller
树的结构, 这样, 它能够为每个场景的 view-model
提供所需要的 model
对象. 如果不增加协调器, 那么 view controller
间就会有耦合.
实际项目中是否引入协调器, 得看具体情况. 如果是针对不是那么复杂的功能做重构, 太复杂的架构反而是画蛇添足.
回到 React Native
项目, 如果 JavaScript
单页应用功能越来越复杂, 我们同样要处理功能模块解耦, 更细一点, 处理各种变化的 state
. 这些 state
可能包括服务器响应数据, 缓存数据, 也包括 UI 状态, 如被选中的标签, 是否显示加载动效或者分页器等等.
所以我们选择了 Redux.
Redux 是什么 ?
Redux
是 JavaScrip
状态容器, 提供可预测化的 state
管理.
在 Redux
中, 这些 state
, 也可以称之为 model
数据.
通过 action
(交互逻辑, 显示逻辑), 更改不同的 state
, 最后显示在界面上.
在下面代码中, POPULAR_REFRESH
和 POPULAR_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](https://upload-images.jianshu.io/upload_images/1208639-017cd0fd95caf8ad.jpg)
-
- 用户操作View, 通过
dispatch
方法, 发出Action
.
-
Action
可以是网络请求, 交互逻辑等.
- 用户操作View, 通过
-
-
Store
自动调用Reducer
, 并且传入两个参数(当前State
和收到的Action
),Reducer
会返回新的State
.
- 如果有
Middleware
,Store
会将当前State
和收到的Action
传递给Middleware
,Middleware
会调用Reducer
然后返回新的State
.
-
-
-
State
一旦有变化,Store
就会调用监听函数, 更新View
.
-
在整个流程中, 数据都是单向流动的.
Redux 的三原则
-
Redux
应用中所有的state
都以一个对象树的形式存储在一个 单一 的store
中. -
state
是 只读 的: 唯一改变state
的办法是触发action
,action
是一个描述发生什么的对象. - 使用纯函数来执行修改: 为了描述
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-redux
是 Redux
官方提供的 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-thunk
或redux-promise
这类支持异步的 moddleware 都包装了store
的dispatch()
方法. 因此我们可以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 :).