目录
- 什么是Redux
- Redux 优点
- Redux 的三个基本原则
- Redux 有那几部分组成
- Action
- reducer
- Store
- 异步Action
- 配置React Native与Redux开发
- 第一步
- 第二步
- 第三步
什么是Redux
Redux 是 JavaScript 状态容器,提供可预测化的状态管理,可以让你构建一致化的应用,运行于不同的环境(客户端、服务器、原生应用),并且易于测试。数据是单向流动的。
工作流程
- 用户(操作View)发出Action,发出方式就用到了dispatch方法;
- 然后,Store自动调用Reducer,并且传入两个参数(当前State和收到的Action),Reducer会返回新的State,如果有Middleware,Store会将当前State和收到的Action传递给Middleware,Middleware会调用Reducer 然后返回新的State;
- State一旦有变化,Store就会调用监听函数,来更新View;
Middleware:可以让你在reducer
执行前与执行后进行拦截并插入代码,来达到操作action和Store的目的,这样一来就很容易实现灵活的日志打印、错误收集、API请求、路由等操作。
Redux优点
- 可预测: 始终有一个唯一的准确的数据源(single source of truth)就是store,通过actions和reducers来保证整个应用状态同步,做到绝不混乱
- 易维护: 具备可预测的结果和严格的组织结构让代码更容易维护
- 易测试: 编写可测试代码的首要准则是编写可以仅做一件事并且独立的小函数(single responsibility principle),Redux的代码几乎全部都是这样的函数:短小·纯粹·分离
Redux 的三个基本原则
- 单一数据源:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中;
- State 是只读的:唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象;
- 使用纯函数来执行修改:为了描述 action 如何改变 state tree ,你需要编写 reducers;
Redux 有那几部分组成
action:action就是一个描述发生什么的对象;
reducer:形式为 (state, action) => state 的纯函数,功能是根据action 修改state 将其转变成下一个 state;
store:用于存储state,你可以把它看成一个容器,整个应用只能有一个store。
Action
Action
是把数据从应用传到 store
的有效载荷。它是 store
数据的唯一来源,也就是说要改变store中的state就需要触发一个action。
Action 本质上一个普通的JavaScript
对象。action
内必须使用一个字符串类型的type
字段来表示将要执行的动作,除了 type 字段外,action 对象的结构完全由你自己决定。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
//action
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
提示:使用单独的模块或文件来定义 action type 常量并不是必须的,甚至根本不需要定义。对于小应用来说,使用字符串做 action type 更方便些。不过,在大型应用中把它们显式地定义成常量还是利大于弊的。
reducer
reducer是根据action 修改state 将其转变成下一个 state,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。
(previousState, action) => newState
保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
提示:reducer 是纯函数。它仅仅用于计算下一个 state。它应该是完全可预测的:多次传入相同的输入必须产生相同的输出。它不应做有副作用的操作,如 API 调用或路由跳转。这些应该在 dispatch action 前发生。
拆分reducer:根据不同模块的划分去查分reducer,减少冗长,让代码简洁。
合并reducer:经过上述的步骤我们将一个大的reducer拆分成了不同的小的reducer,但redux原则是只允许一个根reducer,接下来我们需要将这几个小的reducer聚合到一个跟reducer中。
这里我们需要用到Redux 提供的combineReducers(reducers)
。
combineReducers()
所做的只是生成一个函数,这个函数来调用你的一系列 reducer
,每个 reducer
根据它们的 key
来筛选出 state
中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。没有任何魔法。正如其他 reducers,如果 combineReducers()
中包含的所有 reducers 都没有更改 state,那么也就不会创建一个新的对象
Store
是存储state的容器,Store 会把两个参数(当前的 state 树和 action)传入 reducer。
store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action)方法更新 state:我们可以在任何地方调用 store.dispatch(action),包括组件中、XMLHttpRequest 回调中、甚至定时器中;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
我们使用 combineReducers()
将多个 reducer 合并成为一个。现在我们通过Redux的 createStore()
来创建一个Store。
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
异步Action
上文中所讲的Action都是基于同步实现的,那么对于网络请求数据库加载等应用场景同步Action显然是不适用的,对此我们需要用到异步Action。
我们可将异步Action简答理解为:在Action中进行异步操作等操作返回后再dispatch一个action。
为了使用异步
action
我们需要引入redux-thunk
库,redux-thunk是为Redux提供异步action支持的中间件。
使用redux-thunk
npm install --save redux-thunk
import thunk from 'redux-thunk'
let middlewares = [
thunk
]
//添加异步中间件redux-thunk
let createAppStore = applyMiddleware(...middlewares)(createStore)
创建异步Action
export function onSearch(inputKey, token, popularKeys) {
return dispatch => {
dispatch({type: Types.SEARCH_REFRESH});
fetch(genFetchUrl(inputKey)).then(response => {//如果任务取消,则不做任何处理
return checkCancel(token) ? response.json() : null;
}).then(responseData => {
if (!checkCancel(token, true)) {//如果任务取消,则不做任何处理
return
}
if (!responseData || !responseData.items || responseData.items.length === 0) {
dispatch({type: Types.SEARCH_FAIL, message: inputKey + '什么都没找到'});
return
}
let items = responseData.items;
getFavoriteKeys(inputKey, dispatch, items, token, popularKeys);
}).catch(e => {
console.log(e);
dispatch({type: Types.SEARCH_FAIL, error: e});
})
}
}
异步数据流
默认情况下,createStore() 所创建的 Redux store 没有使用 middleware,所以只支持 同步数据流。
你可以使用 applyMiddleware() 来增强 createStore()。它可以帮助你用简便的方式来描述异步的 action。
像 redux-thunk 或 redux-promise 这样支持异步的 middleware 都包装了 store 的 dispatch() 方法,以此来让你 dispatch 一些除了 action 以外的其他内容,例如:函数或者 Promise。你所使用的任何 middleware 都可以以自己的方式解析你 dispatch 的任何内容,并继续传递 actions 给下一个 middleware。比如,支持 Promise 的 middleware 能够拦截 Promise,然后为每个 Promise 异步地 dispatch 一对 begin/end actions。
当 middleware 链中的最后一个 middleware 开始 dispatch action 时,这个 action 必须是一个普通对象;
配置React Native与Redux开发
第一步:在使用 React Navigation 的项目中,想要集成 redux 就必须要引入 react-navigation-redux-helpers
这个库。
第二步:配置Navigator
/**
* 1.初始化react-navigation与redux的中间件,
* 该方法的一个很大的作用就是为reduxifyNavigator的key设置actionSubscribers(行为订阅者)
* 设置订阅者@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L29
* 检测订阅者是否存在@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L97
* @type {Middleware}
*/
export const middleware = createReactNavigationReduxMiddleware(
'root',
state => state.nav
);
/**
* 2.将根导航器组件传递给 reduxifyNavigator 函数,
* 并返回一个将navigation state 和 dispatch 函数作为 props的新组件;
* 注意:要在createReactNavigationReduxMiddleware之后执行
*/
const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');
/**
* State到Props的映射关系
* @param state
*/
const mapStateToProps = state => ({
state: state.nav,//v2
});
/**
* 3.连接 React 组件与 Redux store
*/
export default connect(mapStateToProps)(AppWithNavigationState);
第三步配置Reducer
//1.指定默认state
const navState = RootNavigator.router.getStateForAction(RootNavigator.router.getActionForPathAndParams(rootCom));
/**
* 2.创建自己的 navigation reducer,
*/
const navReducer = (state = navState, action) => {
const nextState = RootNavigator.router.getStateForAction(action, state);
// 如果`nextState`为null或未定义,只需返回原始`state`
return nextState || state;
};
/**
* 3.合并reducer
* @type {Reducer<any> | Reducer<any, AnyAction>}
*/
const index = combineReducers({
nav: navReducer,
theme: theme,
popular: popular,
trending: trending,
favorite: favorite,
language: language,
search: search,
});
export default index;
第四步:配置store
/**
* 自定义log中间件
* https://cn.redux.js.org/docs/advanced/Middleware.html
* @param store
*/
const logger = store => next => action => {
if (typeof action === 'function') {
console.log('dispatching a function');
} else {
console.log('dispatching ', action);
}
const result = next(action);
console.log('nextState ', store.getState());
return result;
};
const middlewares = [
middleware,
logger,
thunk,
];
/**
* 创建store
*/
export default createStore(reducers, applyMiddleware(...middlewares));