-
Notifications
You must be signed in to change notification settings - Fork 0
Redux
Redux
由少量能协同工作的函数组成,它可以日志打印,热加载,时间旅行,同构应用,录制和重放,无需开发者携带任何其它东西。
你的程序的所有状态都以一个对象树的形式存储在单个store
中。唯一改变状态树的方法是触发一个action
,发出一个描述发生了什么的对象。为了描述action
如何改变状态树,你需要编写reducers
。
你应该把要做的修改变成一个普通对象,这个对象被叫做action
,而不是直接修改 state
。然后编写专门的函数来决定每个action
如何改变应用的state
,这个函数被叫做reducer
。
如果你以前使用Flux
,那么你只需要注意一个重要的区别。Redux
没有Dispatcher
且不支持多个store
。相反,只有一个单一的store
和一个单一的根级reduce
函数(reducer)。随着应用不断变大,你应该把根级的reducer
拆成多个小的reducers
,分别独立地操作state
树的不同部分,而不是添加新的stores
。这就像一个React
应用只有一个根级的组件,这个根组件又由很多小组件构成。
用这个架构开发计数器有点杀鸡用牛刀,但它的威力在于做复杂应用和庞大系统时优秀的扩展能力。由于它可以追溯action
所产生的每一次变更,因此才有强大的开发工具。如录制用户会话并回放所有action
来重现它。
- 单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
- 状态只读
惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。整个 state tree 应被视为只读,并需通过 Redux 来更新 state 和订阅更新。
- 用纯函数来进行修改
为了描述 action 如何改变状态树,你需要编写 reducers。Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。
Flux 常常被表述为(state, action) => state
。从这个意义上说,Redux 无疑是 Flux 架构的实现,且得益于纯函数而更为简单。不同于 Flux ,Redux 并没有 dispatcher 的概念。和 Flux 的另一个重要区别,是Redux 设想你永远不会变动你的数据。你可以很好地使用普通对象和数组来管理状态,而不是在多个 reducer 里变动数据,你应该在 reducer 中返回一个新对象来更新 state。
Redux 并不在意你如何存储 state,state 可以是普通对象,不可变对象,或者其它类型。注意,即便你使用支持cursor
的不可变库,也不应在 Redux 的应用中使用。整个 state tree 应被视为只读,并需通过 Redux 来更新 state 和订阅更新。而如果只是想用 cursor 把 state tree 从 UI tree 解耦并逐步细化 cursor,应使用 selector 来替代。
Action
是把数据从应用送到store
的信息承载体, 它描述有事情发生这一事实。它是store
数据的唯一来源,一般来说你用store.dispatch()
把action
发送到store
。Action
是普通的javascript
对象,action 内使用一个字符串类型的type
字段来表示将要执行的动作。多数情况下,type
会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放action
。除了type
属性外,action
对象的结构由你决定。我们应该尽量减少在action
中传递数据,比如传递数据的索引就比把整个数据对象由action
携带传递更好。
Action创建器
(Action creator)实际是创建action
的函数,redux
中的Action创建器
扮演生成器的角色,它只是简单的返回一个action
对象。
dispatch()
函数可以通过store.dispatch()
直接访问,分发函数(dispatch)
是触发改变状态
的唯一方法。分发函数调用时,它会通过getState()
得到当前的store
状态一并和action
传给reducer
,reducer
的返回值会作为新的store
的状态,发生变化的状态会被监听到并立即通知对应的处理句柄。但dispatch
通常是你使用react-redux
提供的connect()
帮助器来调用。也可以用bindActionCreators()
自动把多个action创建器
绑定到一个dispatch()
函数。bindActionCreators()
的唯一用例是当你想把某个action creator
传入某个没有意识到Redux
的组件,并且你不想往这个组件中传入dispatch
或store
。bindActionCreators()
会返回模仿原生对象的对象,但它的每一个函数会马上分发(dispatch)由相应的action creator
返回的action
。
Redux
只有在reducer
中不允许做有副作用的操作,一旦根(root)reducer
返回新的状态,被订阅的监听器就会被调用,你可以在订阅的监听器里再次调用分发(dispatch)。这一点上Redux
和Flux
有点细微的不同。
在Redux
中,程序的所有状态都存放在一个对象里,建议写代码前先想一下这个对象的结构。程序状态尽可能不要包含UI相关的状态。
在一个复杂应用中,某些数据间可能会相互引用,建议你尽可能地把state
范式化,不要出现嵌套。你可以把程序的state
想像成数据库,每个数据以 ID 为主键,不同数据相互引用时通过 ID 来查找。
reducer
就是一个接受旧的state
和action
并返回一个新的state
的函数。可以简单的表示为(previousState, action) => newState
。简单的说,reducer
根据action
更新state
。
它之所以被称作reducer
是因为它是一个会被传给Array.prototype.reduce(reducer, ?initialValue)
的函数。所以它必须是**纯(pure)函数(对于纯的理解类似于函数式编程思想,即对于给定的输入必然返回固定的输出),并且在reducer
中不能:
- 修改传入的参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random();
在reducer
中,它只计算下一个状态并返回它,没有特殊情况,没有副作用,没有API调用,没有参数修改。
Redux
提供了一个combineReducers()
工具可以把几个独立的reducer
合并起来。combineReducers()
所做的只是生成一个函数,这个函数来调用你的一系列reducer
,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后再把它们的结果合并到一个单独的对象中。
如果你使用ES6,combineReducers
接收一个对象,可以把所有顶级的reducer
放到一个独立的文件中,通过export
暴露出每个reducer
函数,然后使用import * as reducers
得到一个以它们名字作为 key 的 object,比如:
import { combineReducers } from 'redux';
import * as reducers from './reducers';
const todoApp = combineReducers(reducers);
store
是一个把action
和reducer
连在一起的对象。store
有如下职责:
- 维持应用的
state
; - 提供
getState()
方法获取 state; - 允许state通过
dispatch(action)
方法更新; - 通过
subscribe(listener)
注册监听器; - 通过调用被
subscribe(listener)
返回的函数取消刚才被注册的监听器;
一个Redux
应用中只有一个store
对象。如果你想切割你的数据处理逻辑,你应该在reducer
上做文章。如果你使用react-redux
或类似的绑定,你最好不要直接访问你的组件中的store
。store
的定义如下:
type store => {
dispatch: Dispatch
getState: () => State
subscribe: (listener: () => void) => () => void
replaceReducer: (reducer: Reducer) => void
}
当调用createStore()
时,第二个可选的参数可以指定初始状态。
严格的单向数据流是Redux
的核心,这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。同时也鼓励做数据范式化,这样可以避免拷贝多个独立的无法相互引用的数据。
Redux
应用中数据的生命周期遵循下列四个步骤:
- 调用
store.dispatch(action)
,可以在任何地方调用store.dispatch(action)
,包括组件中、XHR 回调中、甚至定时器中; -
Redux store
调用你传入的reducer
函数,Store
会把两个参数传入reducer
: 当前的state
树和action
; - 根
reducer
把多个子reducer
的输出合并进一个单独的state
树; -
Redux store
会保存由根reducer
返回的完整的状态树;
Redux
的React
绑定遵循呈现组件和容器组件相分离的思想。呈现组件和容器组件又可以分别包含其它的呈现组件或容器组件。我们写的多数组件都是呈现组件,但我们也需要生成少量的容器组件来把它们连接到Redux store
。虽然你可以通过store.subscribe()
写出容器组件,但不建议你这么做,因为React Redux
已经做了很多优化工作,建议你用React Redux
的connect()
函数来生成容器组件。
如果容器组件要访问Redux store
,它们可以使用订阅(subscribe)来实现。推荐使用一个专门的Redux
组件<Provider>
,它能使应用中的所有组件都能用到store
而不用把store
在程序中传来传去。你只需要在渲染根组件的时候用一次就可以了。
当调用异步API时,有两个主要的时间点: 发起请求的时刻,和接收到响应的时刻(也可能是超时)。这两个时间点上可能都会改变应用的状态,一般对于任何的API请求,你可能需要分发至少三个不同类型的action
:
-
一个通知
reducer
请求开始的action
。对于这种
action
,reducer 可能会切换一下state
中的isFetching
标记。以此来告诉 UI 来显示进度条。 -
一个通知
reducer
请求成功结束的action
。对于这种
action
,reducer
可能会把接收到的新数据合并到state
中,并重置isFetching
。UI 则会隐藏进度条,并显示接收到的数据。 -
一个通知
reducer
请求失败的action
。对于这种
action
,reducer
可能会重置isFetching
。或者,有些reducer
会保存这些失败信息,并在 UI 里显示出来。
面对异步问题时,首先要考虑的问题是状态形态(state shape),为了使用异步action,你可能要用到某些中间件。通过这个中间件,action
创建器会返回一个函数而非对象,这个被返回的函数可以不用是纯(pure)函数,比如它可以执行某些异步API调用或分发某些action
。
如果不用中间件,Redux store
只支持同步数据流,这是createStore
创建的store
的默认行为,你可以用applyMiddleware()
来增强createStore()
,它能让你方便的传递异步action
。像redux-thunk
或redux-promise
之类的异步中间件封装了store
的dispatch
方法,除了action
外它允许你分发其它的东西,比如函数或promise
。你使用的任意中间件可以解析你分发的所有东西,并且可以把action
按顺序传给下一个中间件。当中间件队列中的最后一个中间件分发一个action
时,这个action
必须是纯对象,这正是同步的Redux数据流
发生的时候。
Redux
的中间件是位于action
被分发后到达reducer
前的那个中间点。Redux
的中间件可以用来做记录日志、创建崩溃报告、调用异步接口或路由等事情。中间件的关键特性是它是可组装的,多个中间件可以组合在一起,每个中间件无需了解它在中间件链条中的前后数据关系。
Redux
的好处是它让状态的改变可预测并且透明化。中间件用了一种嵌套调用的思想。中间件让你能够封装store
的dispatch
方法。
中间件可以表示为:
({getState, dispatch}) => next => action
applyMiddleware()
会返回一个store enhancer
。中间件本质上是一种扩展机制,applyMiddleware
就是Redux
强大的扩展机制的一个例子,中间件比store enhancer
要弱一些,但是它更易于书写。中间件比它实际上听起来要复杂得多,最好的理解中间件的方法是看代码。如果想要试用多个store enhancer
,你需要使用compose()
。
你所写的多数Redux
代码甚至都不用使用Redux
API,多数时候你都是在写函数。
Redux
深蕴Flux store
的本质,从Flux
项目往Redux
迁移是很容易的事情,反之同理。而且两者还可以同时存在。
Redux
的一个原则是从不改变状态,你可能经常要用到Object.assign()
来用新的值或更新的值来创建对象的拷贝。你也可以使用新版JS
中的对象展开语法(object spread syntax),对象展开语法在组装复杂对象时非常有用。
Action
是描述应用中发生了什么事情的纯对象(plain object),是唯一能表达要改变数据意图的东西。把action
设计成你必须分发的纯对象是Redux
的一个基础。Action
的type
定义成字符串常量
有助于项目的长期进展。Action creator
在项目的开发阶段非常有用,它对于解耦合也很有益处。任何数据,不管是UI事件、网络回调或者是诸如websocket等资源最终都要以action
分发。
中间件
让我们能够写出更具表现力的,具备异步特性的action creator
。它可以分发并解析非纯对象(plain object)
。中间件的定义如下:
type MiddlewareAPI = { dispatch: Dispatch, getState: () => State }
type Middleware = (api: MiddlewareAPI) => (next: Dispatch) => Dispatch
一个中间件是一个合成了一个分发函数
(dispatch function)来返回一个新的分发函数的高阶(high-order)函数。它通常会把一个异步action
返回到action
中。
Redux reducer
的API是(state, action) => action
,并没有强迫你要怎样去写一个reducer
。可以把reducer
理解成一个状态的累加器,类似于Array.prototype.reduce()
的概念。reducer
中被累加的值是action
,累加出的结果是state
。
如果要借助Redux
在服务端渲染,Redux
的唯一工作是为应用提供初始状态。
推荐用Mocha
做为测试引擎。
Reselect
是一个创建内存中可组合的选择器函数,Reselect
选择器可以用来通过Redux store
高效地计算派生出的数据。Reselect
提供了一个createSelector()
函数可以创建一个内存中的选择器。createSelector()
的参数是一个选择器数组和一个转换函数(transform function)。如果Redux
的状态树发生了改变,并且状态的改变导致选择器的值也发生了改变,那么Reselect
就会用选择器的值做为参数调用转换函数,转换函数会返还一个结果。如果选择器的值与先前调用选择器时的值一样,它将会返回以前计算出的值而不会调用转换函数。一个Reselect
选择器也可以做为另一个选择器中选择器数组参数中的元素。Reselect
的选择器也可以连接到Redux Store
。
reducer enhancer
是一个以reducer
为参数,返回一个新的reducer
的函数(类似于高阶函数的概念),这个被返回的函数可以处理新的actions
,或持有更多的状态,或者分发它处理不了的action
到内部的action
。combineReducers()
就是一个reducer enhancer
。
redux-undo
也许对于处理undo问题能提供便捷帮助。
分发函数的定义如下:
type BaseDispatch = (a: Action) => Action
type Dispatch = (a: Action | AsyncAction) => any
一个分发函数可能分发一个或多个action
到store
中,也可能不分发action
。store
实例的dispatch
函数是一个基本的分发函数。基本分发函数(base dispatch)永远都是同步发送一个action
到store
的reducer
,同时还会携带由store
返回的当前状态,reducer
将会计算出新的状态。基本分发函数期望action
是一个纯对象。
中间件封装了基础分发函数,它可能会转换、延迟、忽略或者是解析异步action
,然后再把它们传递给下一个中间件。
action creator
的定义是type ActionCreator = (...args: any) => Action | AsyncAction
,如果一个action creator
需要读当前的状态、执行一个API、或者会产生某些副作用(比如路由过渡),那么它就应该返回一个异步action
(async action)。一个异步action
是一个被发送到一个分发函数的值,该值不会被reducer
消费,在它被发送给基础分发函数
前,它将会被中间件转换成一个action
。
store creator
的定义如下:
type StoreCreator = (reducer: Reducer, initialState: ?State) => Store
类似于分发函数,也存在一个基础的store创建器createStore(reducer, initialState)
和store enhancer
,store enhancer
的定义如下:
type StoreEnhancer = (next: StoreCreator) => StoreCreator
一个store enhancer
是一个组合一个store creator
并返回一个新store creator
的高阶函数。这有点类似于中间件。因为store
是一个函数的纯对象集合,而不是某个实例,所有可以很方便地创建它的拷贝,并在这个拷贝上修改数据,这样不会影响到原始store
。多数情况下你不需要写store enhancer
,你可以直接使用Redux
开发工具中提供的那一个。有意思的是Redux
中间件的实现本身也是一个store enhancer
。
记住Redux
本身只涉及到状态管理,在一个真实应用中你可能需要像react-redux
之类的UI绑定。
compose()
从右向左合成函数,它是一个函数式编程的工具,你可以把它用到一系列按行排列的store enhancer
中。compose()
会返回给定函数按从右向左合成后的函数。