现有技术
Redux 是一个混合产物。它和一些设计模式及技术相似,但在关键处也有不同之处。让我们来探索一下这些相似与不同。
Flux
Redux 的灵感来源于 Flux 的几个重要特性。和 Flux 一样,Redux 规定,将模型的更新逻辑全部集中于一个特定的层(Flux 里的 store,Redux 里的 reducer)。Flux 和 Redux 都不允许程序直接修改数据,而是用一个叫作 action
的普通对象来对更改进行描述。
而不同于 Flux ,Redux 并没有 dispatcher 的概念。原因是它依赖纯函数来替代事件处理器。纯函数构建简单,也不需额外的实体来管理它们。你可以将这点看作这两个框架的差异或细节实现,取决于你怎么看 Flux。Flux 常常被表述为 (state, action) => state
。从这个意义上说,Redux 无疑是 Flux 架构的实现,且得益于纯函数使其更简单。
和 Flux 的另一个重大区别,是 Redux 认为你永远不会变更数据。你可以使用简单对象或数组来表示 state,但麻烦的是你要在 reducers 中改变他们。你应该在 reducer 中返回一个新对象来更新 state,你可以配合 object spread 运算符提案 或使用一些库,比如 Immer immutable 变更库。
虽然出于性能方面的考虑,写非纯函数的 reducer 来变更数据在技术上是可行的,但我们并不鼓励这么做。不纯的 reducer 不能实现一些开发特性,如时间旅行、记录/回放或热加载。此外,在大部分实际应用中,这种数据不可变动的特性并不会带来性能问题,就像 Om 显示的,即使对象分配失败,仍可以防止昂贵的重渲染和重计算。而得益于 reducer 是纯函数,应用内的变化更是一目了然。
因为 Redux 的价值,Flux 的创始人们高度赞扬了 Redux
Elm
Elm 是一种函数式编程语言,是由 Evan Czaplicki 受 Haskell 语言的启发开发的。它遵守一种 “model view update” 的架构,更新遵循 (state, action) => state
的规则。Elm 的 “updater” 与 Redux 里的 reducer 充当相同角色。
不同于 Redux,Elm 是一门语言,因此它在执行纯函数,静态类型,不可变动性,action 和模式匹配(使用 case
表达式)等方面更具优势。即使你不打算使用 Elm,也可以读一读 Elm 的架构,尝试一把。基于此,有一个有趣的的项目使用 JavaScript 库实现类似想法 。我们可以从中获得更多启发,从而更好地使用 Redux!为了更加熟悉 Elm 的静态类型,Redux 可以使用一个类似 Flow 的渐进类型解决方案 。
Immutable
Immutable 是一个可实现持久数据结构的 JavaScript 库。它性能很好,并且命名符合 JavaScript API 的语言习惯 。
(注意,尽管 Immutable.js 帮助启发了 Redux,但现在我们推荐使用 Immer 做 immutable 更新。)
Redux 并不在意你如何存储 state,state 可以是普通对象,Immutable 对象,或者其它类型。 为了从 server 端写同构应用或 hydarate 它们的 state ,你可能要用到序列化或反序列化的机制。但除此以外,你可以使用任何数据存储的库,只要它支持数据的不可变性。比如,对于 Redux state ,Backbone 并无意义,因为 Backbone model 是可变的。
注意,即便你使用支持 cursor 的不可变库,也不应在 Redux 的应用中使用。整个 state tree 应被视为只读,并需通过 Redux 来更新 state 和订阅更新。因此,通过 cursor 来改写,对 Redux 来说没有意义。而如果只是想用 cursor 把 state tree 从 UI tree 解耦并逐步细化 cursor,应使用 selector 来替代。 Selector 是可组合的 getter 函数组。具体可参考 reselect,这是一个优秀、简洁的可组合 selector 的实现。
Baobab
Baobab 是另一个流行的库,实现了数据不可变特性的 API,用以更新纯 JavaScript 对象。你当然可以在 Redux 中使用它,但两者一起使用并没有什么优势。
Baobab 所提供的大部分功能都与使用 cursor 更新数据相关,而 Redux 更新数据的唯一方法是 dispatch 一个 action 。可见,两者用不同方法,解决的却是同样的问题,相互并无增益。
不同于 Immutable ,Baobab 在引擎下还不能实现任何特别有效的数据结构,同时使用 Baobab 和 Redux 并无裨益。这种情形下,使用普通对象会更简便。
RxJS
RxJS 是管理复杂异步应用非常优秀的方案。事实上还有致力于构建将人机交互作模拟为相互依赖的可观测变量的库。
RxJS 和 Redux 同时使用有意义么?当然!它们配合得很好。将 Redux store 视作可观察变量非常简便,例如:
function toObservable(store) {
return {
subscribe({ next }) {
const unsubscribe = store.subscribe(() => next(store.getState()))
next(store.getState())
return { unsubscribe }
}
}
}
使用类似方法,你可以组合不同的异步流,将其转化为 action ,再传递给 store.dispatch()
。
问题在于: 在已经使用了 Rx 的情况下,你真的需要 Redux 吗? 不一定。通过 Rx 重新实现 Redux 并不难。有人说仅需使用一两句的 .scan()
方法即可。极有可能!
如果你仍有疑虑,可以去查看 Redux 的源代码 (并不多) 以及生态系统 (例如开发者工具)。如果你无意于此,却还想继续使用相应式的数据流,可以去探索一下 Cycle 这样的库,或把它结合到 Redux 中。记得反馈给我们它运作得如何!