http://gaearon.github.io/redux/index.html ,文档在 http://rackt.github.io/redux/index.html 。本文不是官方文档的翻译。你可以在阅读官方文档之前和之后阅读本文,以加深其中的重点概念。
根据该项目源码的习惯,示例都是基于 ES2015 的语法来写的。
Redux 是应用状态管理服务。虽然本身受到了 Flux 很深的影响,但是其核心概念却非常简单,就是 Map/Reduce 中的 Reduce。
我们看一下 Javascript 中 @H_403_7@Array.prototype.reduce 的用法:
const initState = '';
const actions = ['a','b','c'];
const newState = actions.reduce(
( (prevState,action) => prevState + action ),initState
);
从 Redux 的角度来看,应用程序的状态类似于上面函数中的 @H_403_7@initState 和 @H_403_7@newState 。给定 @H_403_7@initState 之后,随着 @H_403_7@action 的值不断传入给计算函数,得到新的 @H_403_7@newState。
这个计算函数被称之为 @H_403_7@Reducer,就是上例中的 @H_403_7@(prevState,action) => prevState + action。
@H_502_44@Immutable StateRedux 认为,一个应用程序中,所有应用模块之间需要共享访问的数据,都应该放在 @H_403_7@State 对象中。这个应用模块可能是指 React Components,也可能是你自己访问 AJAX API 的代理模块,具体是什么并没有一定的限制。@H_403_7@State 以 “树形” 的方式保存应用程序的不同部分的数据。这些数据可能来自于网络调用、本地数据库查询、甚至包括当前某个 UI 组件的临时执行状态(只要是需要被不同模块访问)、甚至当前窗口大小等。
Redux 没有规定用什么方式来保存 @H_403_7@State,可能是 Javascript 对象,或者是 Immutable.js 的数据结构。但是有一点,你最好确保 State 中每个节点都是 Immutable 的,这样将确保 State 的消费者在判断数据是否变化时,只要简单地进行引用比较即可,例如:
newState.todos === prevState.todos
从而避免 Deep Equal 的遍历过程。
为了确保这一点,在你的 @H_403_7@Reducer 中更新 @H_403_7@State 成员需要这样做:
`let myStuff = [ {name: 'henrik'} ] myStuff = [...mystuff,{name: 'js lovin fool']`
@H_403_7@myStuff 是一个全新的对象。
如果更新的是 Object ,则:
let counters = {
faves: 0,forward: 20,}
// this creates a brand new copy overwriting just that key
counters = {...counters,faves: counters.faves + 1}
而不是:
counters.faves = counters.faves + 1}
要避免对 Object 的 in-place editing。数组也是一样:
let todos = [
{ id: 1,text: 'have lunch'}
]
todos = [...todos,{ id: 2,text: 'buy a cup of coffee'} ]
而不是:
let todos = [
{ id: 1,text: 'have lunch'}
]
todos.push({ id: 2,text: 'buy a cup of coffee'});
遵循这样的方式,无需 Immutable.js 你也可以让自己的应用程序状态是 Immutable 的。
在 Redux 中,@H_403_7@State 只能通过 @H_403_7@action 来变更。@H_403_7@Reducer 就是根据 @H_403_7@action 的语义来完成 @H_403_7@State 变更的函数。@H_403_7@Reducer 的执行是同步的。在给定 @H_403_7@initState 以及一系列的 @H_403_7@actions,无论在什么时间,重复执行多少次 @H_403_7@Reducer,都应该得到相同的 @H_403_7@newState。这使得你的应用程序的状态是可以被 Log 以及 Replay 的。这种确定性,降低了前端开发所面临的复杂状态的乱入问题。确定的状态、再加上 Hot-Reloaidng 和相应的 Dev-Tool,使得前端应用的可控性大大增强了。
@H_502_44@State 结构设计Redux (Flux) 都建议在保存 @H_403_7@State 数据的时候,应该尽可能地遵循范式,避免嵌套数据结构。如果出现了嵌套的对象,那么尽量通过 ID 来引用。
假设远程服务返回的数据是这样的:
[{
id: 1,title: 'Some Article',author: {
id: 1,name: 'Dan'
}
},{
id: 2,title: 'Other Article',name: 'Dan'
}
}]
那么,转换成以下形式会更有效率:
{ result: [1,2],entities: { articles: { 1: { id: 1,author: 1 },2: { id: 2,author: 1 } },users: { 1: { id: 1,name: 'Dan' } } } }
范式化的存储让你的数据的一致性更好,上例中,如果更新了@H_403_7@users[1].name,那么在显示 @H_403_7@articles 的 component 中,作者姓名也被更新了。
其实传统关系数据库的设计原则就是如此,只不过随着对数据分布能力和水平扩展性的要求(放弃了一定程度的数据一致性),服务端数据的冗余越来越多。但是回到客户端,由于需要保存的数据总量不大(往往就是用户最近访问数据的缓存),也没有分布式的要求,因此范式化的数据存储就更有优势了。除了可以收获一致性,还可以减少存储空间(存储空间在客户端更加宝贵)。
除此之外,范式化的存储也利于后面讲到的 @H_403_7@Reducer 局部化,便于将大的 @H_403_7@Reducer 分割为一系列小的 @H_403_7@Reducers。
由于服务器端返回的 JSON 数据(现在常见的方式)往往是冗余而非范式的,因此,可能需要一些工具来帮助你转换,例如:https://github.com/gaearon/normalizr , 虽然很多时候自己控制会更有效一些。
@H_502_44@Reducer下面我们以熟悉 @H_403_7@todoApp 来看一下 @H_403_7@Reducer 的工作方式:
function todoAppReducer(state = initialState,action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({},state,{
visibilityFilter: action.filter
});
case ADD_TODO:
return Object.assign({},{
todos: [...state.todos,{
text: action.text,completed: false
}]
});
default:
return state;
}
}
这个例子演示了 @H_403_7@Reducers 是如何根据传入的 @H_403_7@action.type 分别更新不同的 @H_403_7@ State 字段。
如果当应用程序中存在很多 @H_403_7@action.type 的时候,通过一个 @H_403_7@Reducer 和巨型 @H_403_7@ switch 显然会产生难以维护的代码。此时,比较好的方法就是通过组合小的 @H_403_7@Reducer 来产生大的 @H_403_7@Reducer,而每个小 @H_403_7@Reducer 只负责处理 @H_403_7@State 的一部分字段。如下例:
import { combineReducers } from 'redux';
const todoAppReducer = combineReducers({
visibilityFilter: visibilityFilterReducer
todos: todosReducer
});
@H_403_7@visibilityFilterReducer 和 @H_403_7@todosReducer 是两个小 @H_403_7@Reducers,其中一个如下:
function visibilityFilterReducer(state = SHOW_ALL,action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter;
default:
return state;
}
}
@H_403_7@visibilityFilterReducer 仅仅负责处理 @H_403_7@State.visibilityFilter 字段的状态(通过 @H_403_7@action.type 为 @H_403_7@SET_VISIBILITY_FILTER 的 action 来改变)。@H_403_7@Reducers 划分是通过向 @H_403_7@combineReducers 传递如下形式的参数实现的:
{ field1: reducerForField1,field2: reducerForField2 }
@H_403_7@filed1 和 @H_403_7@filed2 表示 @H_403_7@State 中的字段,@H_403_7@reducerForField1 和 @H_403_7@reducerForField2 是对应的 @H_403_7@Reducers,每个 @H_403_7@Reducers 将仅仅获得 @H_403_7@State.field1 或者 @H_403_7@state.field2 的值,而看不到 @H_403_7@State 下的其他字段的内容。响应的返回结果也会被合并到对应的 @H_403_7@State 字段中。每个 @H_403_7@Reducer 如果遇到自己不能处理的 @H_403_7@action,那么必须原样返回传入的 @H_403_7@state,或者该 @H_403_7@Reducer 设定的初始状态(如果传入的 @H_403_7@state 是 @H_403_7@ undefined)。
使用 @H_403_7@combineReducers 的前提是,每一个被组合的 @H_403_7@Reducer 仅仅和 @H_403_7@State 的一部分数据相关,例如:todos @H_403_7@Reducer 只消费 @H_403_7@State.todos 数据,也只产生 @H_403_7@State.todos 数据。这个基本的原则和上面提到的“State 结构设计”范式相结合,可以满足我们大部分需求。
不过,有时我们就是需要在一个 @H_403_7@Reducer 之中访问另外一个 @H_403_7@Reducer 负责的 @H_403_7@state,这需要我们创建更上一层的 @H_403_7@Reducer(Root Reducer) 来控制这个过程,例如:
function a(state,action) { }
function b(state,action,a) { } // depends on a's state
function something(state = {},action) {
let a = a(state.a,action);
let b = b(state.b,a); // note: b depends on a for computation
return { a,b };
}
在这个例子中,我们有两个 @H_403_7@Reducers,@H_403_7@a 和 @H_403_7@b,其中,@H_403_7@b 在计算自己的 @H_403_7@state 的还需要依赖 @H_403_7@a 的计算结果。因此,我们就不能依靠 @H_403_7@combineReducers 来完成这种需求,而是需要自己写 Root Reducer 了。reduce-reducers 也可以帮我们完成类似的任务:
var reducers = reduceReducers(
combineReducers({
router: routerReducer,customers,stats,dates,filters,ui
}),// cross-cutting concerns because here `state` is the whole state tree
(state,action) => {
switch (action.type) {
case 'SOME_ACTION':
const customers = state.customers;
const filters = state.filters;
// ... do stuff
}
}
);
上面的例子里,在 @H_403_7@combineReducers 的基础上,如果某些 @H_403_7@action 需要触发跨 @H_403_7@ Reducers 的状态改变,则可以用上面的写法。reduce-reducers 组合(每个参数就是一个 @H_403_7@Reducer)的每一个 @H_403_7@Reducer 都可以获取整个 @H_403_7@State,所以请不要滥用(请参见相关讨论:https://github.com/reactjs/redux/issues/749 ),在大部分情况下,如果严格遵循数据范式,通过计算的方法获得跨越 @H_403_7@Reducers 的状态是推荐的方法(http://redux.js.org/docs/recipes/ComputingDerivedData.html )。
一个 @H_403_7@Reducer 可以处理多种 @H_403_7@action.type,而 一种 @H_403_7@action.type 也可能被多个 @H_403_7@Reducers 处理,这是多对多的关系。以下 Helper 函数可以简化 @H_403_7@Reducer 的创建过程:
function createReducer(initialState,handlers) {
return function reducer(state = initialState,action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state,action);
} else {
return state;
}
}
}
export const todosReducer = createReducer([],{
[ActionTypes.ADD_TODO](state,action) {
let text = action.text.trim();
return [...state,text];
}
}
@H_502_44@Store
在 Redux 中,@H_403_7@Store 对象就是用来维护应用程序状态的对象。构造 @H_403_7@Store 对象,仅需要提供一个 Reducer 函数即可。如前所述,这个 @H_403_7@Reducer 函数是负责解释 @H_403_7@Action 对象的语义,从而改变其内部状态(也就是应用程序的状态)。
- @H_403_7@store.getState(): 获取最近的内部状态对象。
- @H_403_7@store.dispatch(action): 将一个 @H_403_7@action 对象发送给 @H_403_7@reducer。
一个次要方法为:@H_403_7@const unsure = store.subscribe(listener),用来订阅状态的变化。在 React + Redux 的程序中,并不推荐使用 @H_403_7@store.subscribe 。但是如果你的应用程序是基于 Observable 模式的,则可以用这个方法来进行适配;例如,你可以通过这个方法将 Redux 和你的 FRP (Functional Reactive Programming) 应用结合。
下面这个例子演示了 @H_403_7@Store 是如何建立的:
import { combineReducers,createStore } from 'redux';
import * as reducers from './reducers';
const todoAppReducer = combineReducers(reducers);
const store = createStore(todoAppReducer); // Line 5
store.dispatch({type: 'ADD_TODO',text: 'Build Redux app'});
我们也可以在 @H_403_7@createStore 的时候为 @H_403_7@Store 指定一个初始状态,例如替换第 5 行为:
const store = createStore(reducers,window.STATE_FROM_SERVER);
这个例子中,初始状态来自于保存在浏览器 @H_403_7@window 对象的 @H_403_7@STATE_FROM_SERVER 属性。这个属性可不是浏览器内置属性,是我们的 Web Server 在返回的页面文件中以内联 JavaScript 方式嵌入的。这是一种 Universal(Isomorphic) Application 的实现方式。Client 无需发起第一个 AJAX API 请求,就可以直接从当前页面中直接获得初始状态。
@H_502_44@Action在 Redux 中,改变 @H_403_7@State 只能通过 @H_403_7@action。并且,每一个 @H_403_7@action 都必须是 Javascript Plain Object,例如:
{
type: 'ADD_TODO',text: 'Build Redux app'
}
Redux 要求 @H_403_7@action 是可以被序列化的,使这得应用程序的状态保存、回放、Undo 之类的功能可以被实现。因此,@H_403_7@action 中不能包含诸如函数调用这样的不可序列化字段。
@H_403_7@action 的格式是有建议规范的,可以包含以下字段:
{
type: 'ADD_TODO',payload: {
text: 'Do something.'
},`Meta: {}`
}
如果 @H_403_7@action 用来表示出错的情况,则可能为:
{
type: 'ADD_TODO',payload: new Error(),error: true
}
@H_403_7@type 是必须要有的属性,其他都是可选的。完整建议请参考 Flux Standard Action(FSA) 定义。已经有不少第三方模块是基于 FSA 的约定来开发了。
@H_502_44@Action Creator事实上,创建 @H_403_7@action 对象很少用这种每次直接声明对象的方式,更多地是通过一个创建函数。这个函数被称为@H_403_7@Action Creator,例如:
function addTodo(text) {
return {
type: ADD_TODO,text
};
}
Action Creator 看起来很简单,但是如果结合上 Middleware 就可以变得非常灵活。
@H_502_44@Middleware如果你用过 Express,那么就会熟悉它的 Middleware 系统。在 HTTP Request 到 Response 处理过程中,一系列的 Express Middlewares 起着不同的作用,有的 Middleware 负责记录 Log,有的负责转换内部异常为特定的 HTTP Status 返回值,有的负责将 Query String 转变到 @H_403_7@request 对象的特定属性。
Redux Middleware 的设计动机确实是来自于 Express 。其主要机制为,建立一个 @H_403_7@store.dispatch 的链条,每个 middleware 是链条中的一个环节,传入的 action 对象逐步处理,直到最后吐出来是 Javascript Plain Object。先来看一个例子:
import { createStore,combineReducers,applyMiddleware } from 'redux';
// applyMiddleware takes createStore() and returns// a function with a compatible API.
let createStoreWithMiddleware = applyMiddleware(
logger,crashReporter
)(createStore);
// Use it like you would use createStore()let todoApp = combineReducers(reducers);
let store = createStoreWithMiddleware(todoApp);
这个例子中,@H_403_7@logger 和 @H_403_7@crashReporter 这两个 Middlewares 分别完成记录 @H_403_7@ action 日志和记录 @H_403_7@action 处理异常的功能。
`// Logs all actions and states after they are dispatched. const logger = store => next => action => { console.log('dispatching',action); let result = next(action); console.log('next state',store.getState()); return result; };`
@H_403_7@logger 是一个 currying (这是函数式编程的一个基本概念,相比 Flux,Redux 大量使用了函数式编程的范式)之后的函数。@H_403_7@next 则是下一个 Middleware 返回的 @H_403_7@dispatch 函数(后面会有分析)。对于一个 Middleware 来说,有了 @H_403_7@store对象,就可以通过 @H_403_7@store.getState() 来获取最近的应用状态以供决策,有了 @H_403_7@next ,则可以控制传递的流程。
ES6 的 Fat Arrow Function 语法(@H_403_7@logger = store => next => action =>)让原本 @H_403_7@function 返回 @H_403_7@function 的语法变得更简洁(I love ☕️script!)。
工业化的 @H_403_7@logger 实现可以参见:https://github.com/fcomb/redux-logger 和 https://github.com/fcomb/redux-diff-logger 。同一个作者写了两个,后面这个支持 @H_403_7@State 的差异显示。
vanilla promise
Middleware 还可以用来对传入的 @H_403_7@action 进行转换,下面这个例子里,传入的 @H_403_7@action 是一个 Promise(显然不符合 @H_403_7@action 必须是 Javascript Plain Object 的要求),因此需要进行转换:
/** * Lets you dispatch promises in addition to actions. * If the promise is resolved,its result will be dispatched as an action. * The promise is returned from `dispatch` so the caller may handle rejection. */
const vanillaPromise = store => next => action => {
if (typeof action.then !== 'function') {
return next(action);
}
// the action is a promise,we should resolve it first
return Promise.resolve(action).then(store.dispatch);
};
这个例子中,如果传入的 @H_403_7@action 是一个 @H_403_7@Promise(即包含 .then 函数,这只是一个粗略的判断),那么就执行这个 @H_403_7@Promise,当 @H_403_7@Promise 执行成功后,将结果直接传递给 @H_403_7@store.dispatch(这个例子中我们短路了 Middlewares 链中的后续环节)。当然,我们要确保 @H_403_7@Promise 的执行结果返回的是 Javascript Plain Object。
这种用法可能并非常用,但是从这个例子我们可以体会到,我们可以定义自己 @H_403_7@action 的语义,然后通过相应的 middleware 进行解析,产生特定的执行逻辑以生成最终的 @H_403_7@action 对象。这个执行过程可能是同步的,也可能是异步的。
从这个例子你可能也会发现,如果们也装载了 @H_403_7@logger Middleware,那么 @H_403_7@logger 可以知道 @H_403_7@Promise action 进入了 @H_403_7@dispatch 函数链条,但是却没有机会知道最终 @H_403_7@Promise 执行成功/失败后发生的事情,因为无论 @H_403_7@Promise 执行成功与否,都会直接调用最原始的 @H_403_7@store.dispatch,没有走 Middlewares 创建的 @H_403_7@dispatch 函数链条。
对 Promise 的完整支持请参见:https://github.com/acdlite/redux-promise 。
Scheduled Dispatch
下面这个例子略微复杂一些,演示了如何延迟执行一个 @H_403_7@action 的 @H_403_7@dispatch。
/** * Schedules actions with { Meta: { delay: N } } to be delayed by N milliseconds. * Makes `dispatch` return a function to cancel the interval in this case. */
const timeoutScheduler = store => next => action => {
if (!action.Meta || !action.Meta.delay) {
return next(action);
}
let intervalId = setTimeout(
() => next(action),action.Meta.delay
);
return function cancel() {
clearInterval(intervalId);
};
};
这个例子中,@H_403_7@timeoutScheduler Middleware 如果发现传入的 @H_403_7@action 参数带有 @H_403_7@Meta.delay 字段,那么就认为这个 @H_403_7@action 需要延时发送。当声明的延迟时间(@H_403_7@Meta.delay)到了,@H_403_7@action 对象才会被送往下一个 Middleware 的 @H_403_7@dispatch 方法。
下面这个 Middleware 非常简单,但是却提供了非常灵活的用法。
Thunk
如果不了解 Thunk 的概念,可以先阅读 http://www.ruanyifeng.com/blog/2015/05/thunk.html 。
@H_403_7@thunk Middleware 的实现非常简单:
const thunk = store => next => action =>
typeof action === 'function' ?
action(store.dispatch,store.getState) :
next(action);
下面的例子装载了 @H_403_7@thunk,且 @H_403_7@dispatch 了一个 Thunk 函数作为 @H_403_7@action。
const createStoreWithMiddleware = applyMiddleware(
logger,
thunk
timeoutScheduler
)(createStore);
const store = createStoreWithMiddleware(combineReducers(reducers));
function addFave(tweetId) {
return (dispatch,getState) => {
if (getState.tweets[tweetId] && getState.tweets[tweetId].faved)
return;
dispatch({type: IS_LOADING});
// Yay,that could be sync or async dispatching
remote.addFave(tweetId).then(
(res) => { dispatch({type: ADD_FAVE_SUCCEED}) },(err) => { dispatch({type: ADD_FAVE_Failed,err: err}) },};
}
store.dispatch(addFave());
这个例子演示了 “收藏” 一条微博的相关的 @H_403_7@action 对象的产生过程。@H_403_7@addFave 作为 @H_403_7@ Action Creator,返回的不是 Javascript Plain Object,而是一个接收 @H_403_7@dispatch 和 @H_403_7@getState 作为参数的 Thunk 函数。
当 @H_403_7@thunk Middleware 发现传入的 @H_403_7@action 是这样的 Thunk 函数时,就会为该函数配齐 @H_403_7@dispatch 和 @H_403_7@getState 参数,让 Thunk 函数得以执行,否则,就调用 @H_403_7@next(action) 让后续 Middleware 获得 @H_403_7@dispatch 的机会。
在 Thunk 函数中,首先会判断当前应用的 @H_403_7@state 中的微博是否已经被 fave 过了,如果没有,才会调用远程方法。
如果需要调用远程方法的话,那么首先发出 @H_403_7@IS_LOADING @H_403_7@action,告诉 关心这个状态的@H_403_7@reducer 一个远程调用启动了。从而让 @H_403_7@reducer 可以更新对应的 @H_403_7@state 属性。这样关心此状态的 @H_403_7@UI Component 则可以据此更新界面提示信息。
远程方法如果调用成功,就会 @H_403_7@dispatch 代表成功的 @H_403_7@action 对象(@H_403_7@{type: ADD_FAVE_SUCCEED}),否则,产生的就是代表失败的 @H_403_7@action 对象(@H_403_7@{type: ADD_FAVE_Failed,err: err}),自然会有关心这两个 @H_403_7@ action 的 @H_403_7@reducer 来据此更新状态。无论如何,@H_403_7@reducer 最后收到的 @H_403_7@ action 对象一定是这种 Javascript Plain Object。
当 Thunk Middleware 处理了 Thunk 函数类型的 @H_403_7@action 之后,如果有配置了其他后续 Middlewares, 则将被跳过去而没有机会执行。
例如:我们的 Middlewares 配置为 @H_403_7@applyMiddleware(logger,thunk,timeoutScheduler),当 @H_403_7@action 是 Thunk 函数时,这个 @H_403_7@action 将没有机会被 @H_403_7@timeoutScheduler Middleware 执行,而 @H_403_7@logger Middleware 则有机会在 @H_403_7@thunk Middleware 之前执行。每个 Middleware 自己决定给不给后续 Middleware 处理的机会。
applyMiddleware
拼装 Middlewares 的工具函数是 @H_403_7@applyMiddleware,该函数的模拟实现如下:
function applyMiddleware(store,middlewares) {
middlewares = middlewares.slice();
middlewares.reverse();
let next = store.dispatch;
middlewares.forEach(middleware =>
next = middleware(store)(next)
);
return Object.assign({},store,{ dispatch: next });
}
结合 Middleware 的写法:
const logger = store => next => action => {
console.log('dispatching',action);
let result = next(action);
console.log('next state',store.getState());
return result;
};
我们可以看到,给 Middleware 传入 @H_403_7@store 和 @H_403_7@next 之后,返回的是一个新的 @H_403_7@ dispatch 方法。而传入的 @H_403_7@next 参数则是之前 Middleware 返回的 @H_403_7@dispatch 函数。这样,在真正传入 @H_403_7@action 之前,我们得到了一个串联在一起的 @H_403_7@dispatch 函数,该函数用来替代原本的@H_403_7@store.dispatch 方法(通过 @H_403_7@Object.assign(...))。Redux Middleware 机制的目的,就是以插件形式改变 @H_403_7@store.dispatch 的行为方式,从而能够处理不同类型的 @H_403_7@action 输入,得到最终的 Javascript Plain Object 形式的 @H_403_7@action 对象。
每一个 Middleware 可以得到:
- 最初的 @H_403_7@store 对象 (@H_403_7@dispatch 属性还是原来的),因此,可以通过 @H_403_7@store.getState 获得最近的状态,以及通过原本的 @H_403_7@dispatch 对象直接发布 @H_403_7@action 对象,跳过其他 Middleware @H_403_7@dispatch 方法(@H_403_7@next)。上面 @H_403_7@vanillaPromise 演示了这样的用法。
- @H_403_7@next 方法: 前一个Middleware 返回的 @H_403_7@dispatch 方法。当前 Middleware 可以根据自己对 @H_403_7@action 的判断和处理结果,决定是否调用 @H_403_7@next 方法,以及传入什么样的参数。
以 @H_403_7@newStore = applyMiddleware(logger,thunk,timeoutScheduler)(store)) 这样的声明为例,@H_403_7@timeoutScheduler 得到的@H_403_7@next 参数就是原始的 @H_403_7@store.dispatch 方法;@H_403_7@thunk 拥有 @H_403_7@timeoutScheduler 返回的 @H_403_7@dispatch 方法,而 @H_403_7@logger 又拥有 @H_403_7@thunk 返回的 @H_403_7@dispatch 方法。最后新生成的 @H_403_7@newStore 的 @H_403_7@dispatch 方法则是 @H_403_7@logger 返回的。因此实际的 @H_403_7@ action 流动的顺序先到 @H_403_7@logger 返回的 @H_403_7@dispatch 方法,再到 @H_403_7@thunk 返回的 @H_403_7@dispatch 方法,最后到 @H_403_7@timeoutScheduler 返回的 @H_403_7@dispatch 方法。
需要注意一点, @H_403_7@logger 因为排在 @H_403_7@dispatch 链条的第一个,因此可以获得进入的每一个 @H_403_7@ action 对象。但是由于其他 Middleware 有可能异步调用 @H_403_7@dispatch (异步调用前一个 Middleware 返回的 @H_403_7@dispatch 方法或者原始的 @H_403_7@store.dispatch ),因此,@H_403_7@logger 并一定有机会知道 @H_403_7@action 最终是怎么传递的。
Middleware 可以有很多玩法的,下面文档列出了 Middleware 的原理和七种Middlewares:http://rackt.github.io/redux/docs/advanced/Middleware.html 。
@H_403_7@store/reducer 是 Redux 的最核心逻辑,而 Middleware 是其外围的一种扩展方式,仅负责 @H_403_7@action 对象的产生。但是由于 Redux 对于核心部分的限定非常严格(保持核心概念的简单):例如,reducer 必须是同步的,实际工程需求所带来的需求都被推到了 Dispatch/Middleware 这部分,官方文档提到的使用方式则起到了”最佳实践”的指导作用。
@H_502_44@Higher-Order StoreMiddleware 是对 @H_403_7@store.dispatch 方法的扩展机制。但有些时候则需要对整个 @H_403_7@store 对象都进行扩充,这就引入了 Higher-Order Store 的概念。
这个概念和 React 的 Higher-Order Component 概念是类似的。https://github.com/gaearon/redux/blob/cdaa3e81ffdf49e25ce39eeed37affc8f0c590f7/docs/higher-order-stores.md ,既提供一个函数,接受 @H_403_7@store 对象作为输入参数,产生一个新的 @H_403_7@store 对象作为返回值。
createStore => createStore'
Redux 建议大家在 Middleware 不能满足扩展要求的前提下再使用 Higher-Order Store,与 Redux 配套的 redux-devtools 就是一个例子。
@H_502_44@Binding To React (React-Native)上面的章节介绍了 Redux 的核心组组件和数据流程,可以通过下图回味一下:
┌──────────────┐
┌─────────────┐ ┌──▶│ subReducer 1 │
┌───▶│Middleware 1 │ │ └──────────────┘
│ └─────────────┘ │ │
│ │ │ ▼
┌─────────────┐ │ │ ┌───────────────┐ ┌──────────┐ │ ┌──────────────┐
│ action' │────┘ ▼ ┌──▶│store.dispatch │───▶│ reducer │───┘ │ subReducer m │
└─────────────┘ ┌─────────────┐ │ └───────────────┘ └──────────┘ └──────────────┘
│Middleware n │ │ │
└─────────────┘ │ │
│ │ ▼
│ │ ┌──────────────┐
└──────────┘ │ state │
plain action └──────────────┘
Redux 解决的是应用程序状态存储以及如何变更的问题,至于怎么用,则依赖于其他模块。关于如何在 React 或者 React-Native 中使用 Redux ,则需要参考 react-redux。
react-redux 是 React Components 如何使用 Redux 的 Binding。下面我们来分析一个具体的例子。
import { Component } from 'react';
export default class Counter extends Component {
render() {
return (
<button onClick={this.props.onIncrement}>
{this.props.value}
</button>
);
}
}
这是一个 React Component,显示了一个按钮。按下这个按钮,就会调用 @H_403_7@this.props.onIncrement。@H_403_7@onIncrement的具体内容在下面的例子中, 起作用为每次调用 @H_403_7@onIncrement 就会 @H_403_7@dispatch @H_403_7@{type: INCREMENT} Action 对象来更新 @H_403_7@Store/State。
在 react-redux 中,这样的 Component 被称为 “Dumb” Component,既其本身对 Redux 完全无知,它只知道从 @H_403_7@ this.props 获取需要的 @H_403_7@Action Creator 并且了解其语义,适当的时候调用该方法。而 “Dumb” Component 需要展现的外部数据也来自于 @H_403_7@this.props。
如何为 “Dumb” Component 准备 @H_403_7@this.props 呢?react-redux 提供的 @H_403_7@connect 函数帮助你完成这个功能:
import { Component } from 'react';
import { connect } from 'react-redux';
import Counter from '../components/Counter';
import { increment } from '../actionsCreators';
// Which part of the Redux global state does our component want to receive as props?
function mapStateToProps(state) {
return {
value: state.counter
};
}
// Which action creators does it want to receive by props?
function mapDispatchToProps(dispatch) {
return {
onIncrement: () => dispatch(increment())
};
}
export default connect( // Line 20
mapStateToProps,mapDispatchToProps
)(Counter);
第 20 行的 @H_403_7@connect将 @H_403_7@state 的某个(些)属性映射到了 @H_403_7@Counter Component 的 @H_403_7@this.props 属性中,同时也把针对特定的@H_403_7@Action Creator 的 @H_403_7@dispatch 方法传递给了 @H_403_7@this.props。这样在 @H_403_7@Counter Component 中仅仅通过 @H_403_7@this.props 就可以完成 action dispatching 和 应用程序状态获取的动作。
如果 connect 函数省掉第二个参数,@H_403_7@connect(mapStateToProps)(Counter),那么 @H_403_7@dispatch 方法会被直接传递给 @H_403_7@this.props。这不是推荐的方式,因为这意味着 @H_403_7@Counter 需要了解 @H_403_7@dispatch 的功能和语义了。
Components 的嵌套
你可以在你的组件树的任何一个层次调用 @H_403_7@connect 来为下层组件绑定状态和 @H_403_7@dispatch 方法。但是仅在你的顶层组件调用 @H_403_7@connect 进行绑定是首选的方法。
Provider Component
上面的例子实际上是不可执行的,因为 @H_403_7@connect 函数其实并没有 Redux @H_403_7@store 对象在哪里。所以我们需要有一个机制让 @H_403_7@connect 知道从你那里获得 @H_403_7@store 对象,这是通过 @H_403_7@Provider Component 来设定的,@H_403_7@Provider Component 也是 react-redux 提供的工具组件。
React.render(
<Provider store={store}>
{() => <MyRootComponent />}
</Provider>,rootEl
);
@H_403_7@Provider Component 应该是你的 React Components 树的根组件。由于 React 0.13 版本的问题,@H_403_7@Provider Component 的子组件必须是一个函数,这个问题将在 React 0.14 中修复。
@H_403_7@Provider Component 和 @H_403_7@connect 函数的配合,使得 React Component 在对 Redux 完全无感的情况下,仅通过 React 自身的机制来获取和维护应用程序的状态。
selector
在上面的例子中,@H_403_7@connect(mapStateToProps,mapDispatchToProps)(Counter) 中的 @H_403_7@ mapStateToProps 函数通过返回一个映射对象,指定了哪些 @H_403_7@Store/State 属性被映射到 React Component 的 @H_403_7@this.props,这个方法被称为 @H_403_7@selector。@H_403_7@selector 的作用就是为 React Components 构造适合自己需要的状态视图。@H_403_7@selector 的引入,降低了 React Component 对 @H_403_7@Store/State 数据结构的依赖,利于代码解耦;同时由于 @H_403_7@selector 的实现完全是自定义函数,因此也有足够的灵活性(例如对原始状态数据进行过滤、汇总等)。
reselect 这个项目提供了带 cache 功能的 @H_403_7@selector。如果 @H_403_7@Store/State 和构造 view 的参数没有变化,那么每次 Component 获取的数据都将来自于上次调用/计算的结果。得益于 @H_403_7@Store/State Immutable 的本质,状态变化的检测是非常高效的。
@H_502_44@总结- Redux 和 React 没有直接关系,它瞄准的目标是应用状态管理。
- 核心概念是 Map/Reduce 中的 Reduce。且 @H_403_7@Reducer 的执行是同步,产生的 @H_403_7@State 是 Immutable 的。
- 改变 @H_403_7@State 只能通过向 Reducer dispatch actions 来完成。
- @H_403_7@State 的不同字段,可以通过不同的 @H_403_7@Reducers 来分别维护。@H_403_7@combineReducers 负责组合这些 @H_403_7@Reducers,前提是每个 @H_403_7@Reducer 只能维护自己关心的字段。
- @H_403_7@Action 对象只能是 Javascript Plain Object,但是通过在 @H_403_7@store 上装载 @H_403_7@middleware,则可以任意定义 @H_403_7@action 对象的形式,反正会有特定的 @H_403_7@middleware 负责将此 @H_403_7@action 对象变为 Javascript Plain Object。可以以@H_403_7@middleware 链条为集中点实现很多控制逻辑,例如 Log,Undo,ErrorHandler 等。
- Redux 仅仅专注于应用状态的维护,@H_403_7@reducer、@H_403_7@dispatch/middleware 是两个常用扩展点、Higher-order Store 则仅针对需要扩展全部 @H_403_7@Store 功能时使用。
- react-redux 是 Redux 针对 React/React-Native 的 Binding,@H_403_7@connect/selector 是扩展点,负责将 @H_403_7@store 中的状态添加到 React @H_403_7@component 的 @H_403_7@props 中。
- Redux 借用了很多函数式编程的思想,了解函数式编程会利于理解其实现原理,虽然使用它不需要了解很多函数式编程的概念。和 Flux 相比,Redux 的概念更精简、约定更严格、状态更确定、而是扩展却更灵活。
- 通过 https://github.com/xgrommx/awesome-redux 可以获得大量参考。
大而全的所有 Redux 参考资料。
https://github.com/xgrommx/awesome-redux
Slack 讨论组
加入 https://reactiflux.slack.com Team,然后选择 redux channel。
原文链接:https://www.f2er.com/react/306103.html