Redux 核心概念

http://gaearon.github.io/redux/index.html ,文档在 http://rackt.github.io/redux/index.html 。本文不是官方文档的翻译。你能够在阅读官方文档以前和以后阅读本文,以加深其中的重点概念。javascript

根据该项目源码的习惯,示例都是基于 ES2015 的语法来写的。php

Redux 是应用状态管理服务。虽然自己受到了 Flux 很深的影响,可是其核心概念却很是简单,就是 Map/Reduce 中的 Reduce。css

咱们看一下 Javascript 中 Array.prototype.reduce 的用法:html

const initState = '';
const actions = ['a', 'b', 'c'];
const newState = actions.reduce(
    ( (prevState, action) => prevState + action ),
    initState
);

从 Redux 的角度来看,应用程序的状态相似于上面函数中的 initStatenewState 。给定 initState 以后,随着 action 的值不断传入给计算函数,获得新的 newState前端

这个计算函数被称之为 Reducer,就是上例中的 (prevState, action) => prevState + actionjava

Immutable State

Redux 认为,一个应用程序中,全部应用模块之间须要共享访问的数据,都应该放在 State 对象中。这个应用模块多是指 React Components,也多是你本身访问 AJAX API 的代理模块,具体是什么并无必定的限制。State 以 “树形” 的方式保存应用程序的不一样部分的数据。这些数据可能来自于网络调用、本地数据库查询、甚至包括当前某个 UI 组件的临时执行状态(只要是须要被不一样模块访问)、甚至当前窗口大小等。react

Redux 没有规定用什么方式来保存 State,多是 Javascript 对象,或者是 Immutable.js 的数据结构。可是有一点,你最好确保 State 中每一个节点都是 Immutable 的,这样将确保 State 的消费者在判断数据是否变化时,只要简单地进行引用比较便可,例如:git

newState.todos === prevState.todos

从而避免 Deep Equal 的遍历过程。github

为了确保这一点,在你的 Reducer 中更新 State 成员须要这样作:数据库

`let myStuff = [
    {name: 'henrik'}
]

myStuff = [...mystuff, {name: 'js lovin fool']`

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 中,State 只能经过 action 来变动。Reducer 就是根据 action 的语义来完成 State 变动的函数。Reducer 的执行是同步的。在给定 initState 以及一系列的 actions,不管在什么时间,重复执行多少次 Reducer,都应该获得相同的 newState。这使得你的应用程序的状态是能够被 Log 以及 Replay 的。这种肯定性,下降了前端开发所面临的复杂状态的乱入问题。肯定的状态、再加上 Hot-Reloaidng 和相应的 Dev-Tool,使得前端应用的可控性大大加强了。

State 结构设计

Redux (Flux) 都建议在保存 State 数据的时候,应该尽量地遵循范式,避免嵌套数据结构。若是出现了嵌套的对象,那么尽可能经过 ID 来引用。

假设远程服务返回的数据是这样的:

[{ id: 1, title: 'Some Article', author: { id: 1, name: 'Dan' } }, { id: 2, title: 'Other Article', author: { id: 1, name: 'Dan' } }]

那么,转换成如下形式会更有效率:

{ result: [1, 2], entities: { articles: { 1: { id: 1, title: 'Some Article', author: 1 }, 2: { id: 2, title: 'Other Article', author: 1 } }, users: { 1: { id: 1, name: 'Dan' } } } }

范式化的存储让你的数据的一致性更好,上例中,若是更新了users[1].name,那么在显示 articles 的 component 中,做者姓名也被更新了。

其实传统关系数据库的设计原则就是如此,只不过随着对数据分布能力和水平扩展性的要求(放弃了必定程度的数据一致性),服务端数据的冗余愈来愈多。可是回到客户端,因为须要保存的数据总量不大(每每就是用户最近访问数据的缓存),也没有分布式的要求,所以范式化的数据存储就更有优点了。除了能够收获一致性,还能够减小存储空间(存储空间在客户端更加宝贵)。

除此以外,范式化的存储也利于后面讲到的 Reducer 局部化,便于将大的 Reducer 分割为一系列小的 Reducers

因为服务器端返回的 JSON 数据(如今常见的方式)每每是冗余而非范式的,所以,可能须要一些工具来帮助你转换,例如:https://github.com/gaearon/normalizr , 虽然不少时候本身控制会更有效一些。

Reducer

下面咱们以熟悉 todoApp 来看一下 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({}, state, { todos: [...state.todos, { text: action.text, completed: false }] }); default: return state; } }

这个例子演示了 Reducers 是如何根据传入的 action.type 分别更新不一样的 State 字段。

若是当应用程序中存在不少 action.type 的时候,经过一个 Reducer 和巨型 switch 显然会产生难以维护的代码。此时,比较好的方法就是经过组合小的 Reducer 来产生大的 Reducer,而每一个小 Reducer 只负责处理 State 的一部分字段。以下例:

import { combineReducers } from 'redux'; const todoAppReducer = combineReducers({ visibilityFilter: visibilityFilterReducer todos: todosReducer });

visibilityFilterReducertodosReducer 是两个小 Reducers,其中一个以下:

function visibilityFilterReducer(state = SHOW_ALL, action) { switch (action.type) { case SET_VISIBILITY_FILTER: return action.filter; default: return state; } }

visibilityFilterReducer 仅仅负责处理 State.visibilityFilter 字段的状态(经过 action.typeSET_VISIBILITY_FILTER 的 action 来改变)。Reducers 划分是经过向 combineReducers 传递以下形式的参数实现的:

{ field1: reducerForField1, field2: reducerForField2 }

filed1filed2 表示 State 中的字段,reducerForField1reducerForField2 是对应的 Reducers,每一个 Reducers 将仅仅得到 State.field1 或者 state.field2 的值,而看不到 State 下的其余字段的内容。响应的返回结果也会被合并到对应的 State 字段中。每一个 Reducer 若是遇到本身不能处理的 action,那么必须原样返回传入的 state,或者该 Reducer 设定的初始状态(若是传入的 stateundefined)。

使用 combineReducers 的前提是,每个被组合的 Reducer 仅仅和 State 的一部分数据相关,例如:todos Reducer 只消费 State.todos 数据,也只产生 State.todos 数据。这个基本的原则和上面提到的“State 结构设计”范式相结合,能够知足咱们大部分需求。

不过,有时咱们就是须要在一个 Reducer 之中访问另一个 Reducer 负责的 state,这须要咱们建立更上一层的 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, action, a); // note: b depends on a for computation return { a, b }; }

在这个例子中,咱们有两个 Reducers, ab,其中,b 在计算本身的 state 的还须要依赖 a 的计算结果。所以,咱们就不能依靠 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 } } );

上面的例子里,在 combineReducers 的基础上,若是某些 action 须要触发跨 Reducers 的状态改变,则能够用上面的写法。reduce-reducers 组合(每一个参数就是一个 Reducer)的每个 Reducer 均可以获取整个 State,因此请不要滥用(请参见相关讨论:https://github.com/reactjs/redux/issues/749 ),在大部分状况下,若是严格遵循数据范式,经过计算的方法得到跨越 Reducers 的状态是推荐的方法(http://redux.js.org/docs/recipes/ComputingDerivedData.html )。


一个 Reducer 能够处理多种 action.type,而 一种 action.type 也可能被多个 Reducers 处理,这是多对多的关系。如下 Helper 函数能够简化 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]; } }

Store

在 Redux 中,Store 对象就是用来维护应用程序状态的对象。构造 Store 对象,仅须要提供一个 Reducer 函数便可。如前所述,这个 Reducer 函数是负责解释 Action 对象的语义,从而改变其内部状态(也就是应用程序的状态)。

所以 Store 对象有两个主要方法,一个次要方法:

  1. store.getState(): 获取最近的内部状态对象。
  2. store.dispatch(action): 将一个 action 对象发送给 reducer

一个次要方法为:const unsure = store.subscribe(listener),用来订阅状态的变化。在 React + Redux 的程序中,并不推荐使用 store.subscribe 。可是若是你的应用程序是基于 Observable 模式的,则能够用这个方法来进行适配;例如,你能够经过这个方法将 Redux 和你的 FRP (Functional Reactive Programming) 应用结合。

下面这个例子演示了 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'});

咱们也能够在 createStore 的时候为 Store 指定一个初始状态,例如替换第 5 行为:

const store = createStore(reducers, window.STATE_FROM_SERVER);

这个例子中,初始状态来自于保存在浏览器 window 对象的 STATE_FROM_SERVER 属性。这个属性可不是浏览器内置属性,是咱们的 Web Server 在返回的页面文件中之内联 JavaScript 方式嵌入的。这是一种 Universal(Isomorphic) Application 的实现方式。Client 无需发起第一个 AJAX API 请求,就能够直接从当前页面中直接得到初始状态。

Action

在 Redux 中,改变 State 只能经过 action。而且,每个 action 都必须是 Javascript Plain Object,例如:

{ type: 'ADD_TODO', text: 'Build Redux app' }

Redux 要求 action 是能够被序列化的,使这得应用程序的状态保存、回放、Undo 之类的功能能够被实现。所以,action 中不能包含诸如函数调用这样的不可序列化字段。

action 的格式是有建议规范的,能够包含如下字段:

{ type: 'ADD_TODO', payload: { text: 'Do something.' }, `meta: {}` }

若是 action 用来表示出错的状况,则可能为:

{ type: 'ADD_TODO', payload: new Error(), error: true }

type 是必需要有的属性,其余都是可选的。完整建议请参考 Flux Standard Action(FSA) 定义。已经有很多第三方模块是基于 FSA 的约定来开发了。

Action Creator

事实上,建立 action 对象不多用这种每次直接声明对象的方式,更多地是经过一个建立函数。这个函数被称为Action Creator,例如:

function addTodo(text) { return { type: ADD_TODO, text }; }

Action Creator 看起来很简单,可是若是结合上 Middleware 就能够变得很是灵活。

Middleware

若是你用过 Express,那么就会熟悉它的 Middleware 系统。在 HTTP Request 到 Response 处理过程当中,一系列的 Express Middlewares 起着不一样的做用,有的 Middleware 负责记录 Log,有的负责转换内部异常为特定的 HTTP Status 返回值,有的负责将 Query String 转变到 request 对象的特定属性。

Redux Middleware 的设计动机确实是来自于 Express 。其主要机制为,创建一个 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);

这个例子中,loggercrashReporter 这两个 Middlewares 分别完成记录 action 日志和记录 action 处理异常的功能。

logger 的代码以下:

`// 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; };`

logger 是一个 currying (这是函数式编程的一个基本概念,相比 Flux,Redux 大量使用了函数式编程的范式)以后的函数。next 则是下一个 Middleware 返回的 dispatch 函数(后面会有分析)。对于一个 Middleware 来讲,有了 store对象,就能够经过 store.getState() 来获取最近的应用状态以供决策,有了 next ,则能够控制传递的流程。

ES6 的 Fat Arrow Function 语法(logger = store => next => action =>)让本来 function 返回 function 的语法变得更简洁(I love ☕️script!)。

工业化的 logger 实现能够参见:https://github.com/fcomb/redux-loggerhttps://github.com/fcomb/redux-diff-logger 。同一个做者写了两个,后面这个支持 State 的差别显示。

vanilla promise

Middleware 还能够用来对传入的 action 进行转换,下面这个例子里,传入的 action 是一个 Promise(显然不符合 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); };

这个例子中,若是传入的 action 是一个 Promise(即包含 .then 函数,这只是一个粗略的判断),那么就执行这个 Promise,当 Promise 执行成功后,将结果直接传递给 store.dispatch(这个例子中咱们短路了 Middlewares 链中的后续环节)。固然,咱们要确保 Promise 的执行结果返回的是 Javascript Plain Object。

这种用法可能并不是经常使用,可是从这个例子咱们能够体会到,咱们能够定义本身 action 的语义,而后经过相应的 middleware 进行解析,产生特定的执行逻辑以生成最终的 action 对象。这个执行过程多是同步的,也多是异步的。

从这个例子你可能也会发现,若是们也装载了 logger Middleware,那么 logger 能够知道 Promise action 进入了 dispatch 函数链条,可是却没有机会知道最终 Promise 执行成功/失败后发生的事情,由于不管 Promise 执行成功与否,都会直接调用最原始的 store.dispatch,没有走 Middlewares 建立的 dispatch 函数链条。

对 Promise 的完整支持请参见:https://github.com/acdlite/redux-promise

Scheduled Dispatch

下面这个例子略微复杂一些,演示了如何延迟执行一个 actiondispatch

/** * 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); }; };

这个例子中,timeoutScheduler Middleware 若是发现传入的 action 参数带有 meta.delay 字段,那么就认为这个 action 须要延时发送。当声明的延迟时间(meta.delay)到了,action 对象才会被送往下一个 Middleware 的 dispatch 方法。

下面这个 Middleware 很是简单,可是却提供了很是灵活的用法。

Thunk

若是不了解 Thunk 的概念,能够先阅读 http://www.ruanyifeng.com/blog/2015/05/thunk.html

thunk Middleware 的实现很是简单:

const thunk = store => next => action => typeof action === 'function' ? action(store.dispatch, store.getState) : next(action);

下面的例子装载了 thunk,且 dispatch 了一个 Thunk 函数做为 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());

这个例子演示了 “收藏” 一条微博的相关的 action 对象的产生过程。addFave 做为 Action Creator,返回的不是 Javascript Plain Object,而是一个接收 dispatchgetState 做为参数的 Thunk 函数。

thunk Middleware 发现传入的 action 是这样的 Thunk 函数时,就会为该函数配齐 dispatchgetState 参数,让 Thunk 函数得以执行,不然,就调用 next(action) 让后续 Middleware 得到 dispatch 的机会。

在 Thunk 函数中,首先会判断当前应用的 state 中的微博是否已经被 fave 过了,若是没有,才会调用远程方法。

若是须要调用远程方法的话,那么首先发出 IS_LOADING action,告诉 关心这个状态的reducer 一个远程调用启动了。从而让 reducer 能够更新对应的 state 属性。这样关心此状态的 UI Component 则能够据此更新界面提示信息。

远程方法若是调用成功,就会 dispatch 表明成功的 action 对象({type: ADD_FAVE_SUCCEED}),不然,产生的就是表明失败的 action 对象({type: ADD_FAVE_FAILED, err: err}),天然会有关心这两个 actionreducer 来据此更新状态。不管如何,reducer 最后收到的 action 对象必定是这种 Javascript Plain Object。

当 Thunk Middleware 处理了 Thunk 函数类型的 action 以后,若是有配置了其余后续 Middlewares, 则将被跳过去而没有机会执行。

例如:咱们的 Middlewares 配置为 applyMiddleware(logger, thunk, timeoutScheduler),当 action 是 Thunk 函数时,这个 action 将没有机会被 timeoutScheduler Middleware 执行,而 logger Middleware 则有机会在 thunk Middleware 以前执行。每一个 Middleware 本身决定给不给后续 Middleware 处理的机会。

applyMiddleware

拼装 Middlewares 的工具函数是 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 传入 storenext 以后,返回的是一个新的 dispatch 方法。而传入的 next 参数则是以前 Middleware 返回的 dispatch 函数。这样,在真正传入 action 以前,咱们获得了一个串联在一块儿的 dispatch 函数,该函数用来替代本来的store.dispatch 方法(经过 Object.assign(...))。Redux Middleware 机制的目的,就是以插件形式改变 store.dispatch 的行为方式,从而可以处理不一样类型的 action 输入,获得最终的 Javascript Plain Object 形式的 action 对象。

每个 Middleware 能够获得:

  1. 最初的 store 对象 (dispatch 属性仍是原来的),所以,能够经过 store.getState 得到最近的状态,以及经过本来的 dispatch 对象直接发布 action 对象,跳过其余 Middleware dispatch 方法(next)。上面 vanillaPromise 演示了这样的用法。
  2. next 方法: 前一个Middleware 返回的 dispatch 方法。当前 Middleware 能够根据本身对 action 的判断和处理结果,决定是否调用 next 方法,以及传入什么样的参数。

newStore = applyMiddleware(logger,thunk,timeoutScheduler)(store)) 这样的声明为例,timeoutScheduler 获得的next 参数就是原始的 store.dispatch 方法;thunk 拥有 timeoutScheduler 返回的 dispatch 方法,而 logger 又拥有 thunk 返回的 dispatch 方法。最后新生成的 newStoredispatch 方法则是 logger 返回的。所以实际的 action 流动的顺序先到 logger 返回的 dispatch 方法,再到 thunk 返回的 dispatch 方法,最后到 timeoutScheduler 返回的 dispatch 方法。

须要注意一点, logger 由于排在 dispatch 链条的第一个,所以能够得到进入的每个 action 对象。可是因为其余 Middleware 有可能异步调用 dispatch (异步调用前一个 Middleware 返回的 dispatch 方法或者原始的 store.dispatch ),所以,logger 并必定有机会知道 action 最终是怎么传递的。

Middleware 能够有不少玩法的,下面文档列出了 Middleware 的原理和七种Middlewares:http://rackt.github.io/redux/docs/advanced/Middleware.html

store/reducer 是 Redux 的最核心逻辑,而 Middleware 是其外围的一种扩展方式,仅负责 action 对象的产生。可是因为 Redux 对于核心部分的限定很是严格(保持核心概念的简单):例如,reducer 必须是同步的,实际工程需求所带来的需求都被推到了 Dispatch/Middleware 这部分,官方文档提到的使用方式则起到了”最佳实践”的指导做用。

Higher-Order Store

Middleware 是对 store.dispatch 方法的扩展机制。但有些时候则须要对整个 store 对象都进行扩充,这就引入了 Higher-Order Store 的概念。

这个概念和 React 的 Higher-Order Component 概念是相似的。https://github.com/gaearon/redux/blob/cdaa3e81ffdf49e25ce39eeed37affc8f0c590f7/docs/higher-order-stores.md ,既提供一个函数,接受 store 对象做为输入参数,产生一个新的 store 对象做为返回值。

createStore => createStore'

Redux 建议你们在 Middleware 不能知足扩展要求的前提下再使用 Higher-Order Store,与 Redux 配套的 redux-devtools 就是一个例子。

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,显示了一个按钮。按下这个按钮,就会调用 this.props.onIncrementonIncrement的具体内容在下面的例子中, 起做用为每次调用 onIncrement 就会 dispatch {type: INCREMENT} Action 对象来更新 Store/State

react-redux 中,这样的 Component 被称为 “Dumb” Component,既其自己对 Redux 彻底无知,它只知道从 this.props 获取须要的 Action Creator 而且了解其语义,适当的时候调用该方法。而 “Dumb” Component 须要展示的外部数据也来自于 this.props

如何为 “Dumb” Component 准备 this.props 呢?react-redux 提供的 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 行的 connectstate 的某个(些)属性映射到了 Counter Component 的 this.props 属性中,同时也把针对特定的Action Creatordispatch 方法传递给了 this.props。这样在 Counter Component 中仅仅经过 this.props 就能够完成 action dispatching 和 应用程序状态获取的动做。

若是 connect 函数省掉第二个参数,connect(mapStateToProps)(Counter),那么 dispatch 方法会被直接传递给 this.props。这不是推荐的方式,由于这意味着 Counter 须要了解 dispatch 的功能和语义了。

Components 的嵌套

你能够在你的组件树的任何一个层次调用 connect 来为下层组件绑定状态和 dispatch 方法。可是仅在你的顶层组件调用 connect 进行绑定是首选的方法。

Provider Component

上面的例子其实是不可执行的,由于 connect 函数其实并无 Redux store 对象在哪里。因此咱们须要有一个机制让 connect 知道从你那里得到 store 对象,这是经过 Provider Component 来设定的,Provider Component 也是 react-redux 提供的工具组件。

React.render( <Provider store={store}> {() => <MyRootComponent />} </Provider>, rootEl );

Provider Component 应该是你的 React Components 树的根组件。因为 React 0.13 版本的问题,Provider Component 的子组件必须是一个函数,这个问题将在 React 0.14 中修复。

Provider Component 和 connect 函数的配合,使得 React Component 在对 Redux 彻底无感的状况下,仅经过 React 自身的机制来获取和维护应用程序的状态。

selector

在上面的例子中,connect(mapStateToProps,mapDispatchToProps)(Counter) 中的 mapStateToProps 函数经过返回一个映射对象,指定了哪些 Store/State 属性被映射到 React Component 的 this.props,这个方法被称为 selectorselector 的做用就是为 React Components 构造适合本身须要的状态视图。selector 的引入,下降了 React Component 对 Store/State 数据结构的依赖,利于代码解耦;同时因为 selector 的实现彻底是自定义函数,所以也有足够的灵活性(例如对原始状态数据进行过滤、汇总等)。

reselect 这个项目提供了带 cache 功能的 selector。若是 Store/State 和构造 view 的参数没有变化,那么每次 Component 获取的数据都未来自于上次调用/计算的结果。得益于 Store/State Immutable 的本质,状态变化的检测是很是高效的。

总结

  1. Redux 和 React 没有直接关系,它瞄准的目标是应用状态管理。
  2. 核心概念是 Map/Reduce 中的 Reduce。且 Reducer 的执行是同步,产生的 State 是 Immutable 的。
  3. 改变 State 只能经过向 Reducer dispatch actions 来完成。
  4. State 的不一样字段,能够经过不一样的 Reducers 来分别维护。combineReducers 负责组合这些 Reducers,前提是每一个 Reducer 只能维护本身关心的字段。
  5. Action 对象只能是 Javascript Plain Object,可是经过在 store 上装载 middleware,则能够任意定义 action 对象的形式,反正会有特定的 middleware 负责将此 action 对象变为 Javascript Plain Object。能够以middleware 链条为集中点实现不少控制逻辑,例如 Log,Undo, ErrorHandler 等。
  6. Redux 仅仅专一于应用状态的维护,reducerdispatch/middleware 是两个经常使用扩展点、Higher-order Store 则仅针对须要扩展所有 Store 功能时使用。
  7. react-redux 是 Redux 针对 React/React-Native 的 Binding,connect/selector 是扩展点,负责将 store 中的状态添加到 React componentprops 中。
  8. Redux 借用了不少函数式编程的思想,了解函数式编程会利于理解其实现原理,虽然使用它不须要了解不少函数式编程的概念。和 Flux 相比,Redux 的概念更精简、约定更严格、状态更肯定、而是扩展却更灵活。
  9. 经过 https://github.com/xgrommx/awesome-redux 能够得到大量参考。

其余参考

大而全的全部 Redux 参考资料。

https://github.com/xgrommx/awesome-redux

Slack 讨论组

加入 https://reactiflux.slack.com Team,而后选择 redux channel。



文/jacobbubu(简书做者)
原文连接:http://www.jianshu.com/p/3334467e4b32
著做权归做者全部,转载请联系做者得到受权,并标注“简书做者”。