Flux 架构已然让人以为有些迷惑,而比 Flux 更让人摸不着头脑的是 Flux 与 Redux 的区别。Redux 是一个基于 Flux 思想的新架构方式,本文将探讨它们的区别。react
若是你尚未看过这篇关于 Flux 的文章(译者注:也能够参考这篇),你应该先阅读一下。redux
Redux 解决的问题和 Flux 同样,但 Redux 能作的还有更多。数组
和 Flux 同样,Redux 让应用的状态变化变得更加可预测。若是你想改变应用的状态,就必须 dispatch 一个 action。你没有办法直接改变应用的状态,由于保存这些状态的东西(称为 store)只有 getter 而没有 setter。对于 Flux 和 Redux 来讲,这些概念都是类似的。网络
那么为何要新设计一种架构呢?Redux 的创造者 Dan Abramov 发现了改进 Flux 架构的可能。他想要一个更好的开发者工具来调试 Flux 应用。他发现若是稍微对 Flux 架构进行一些调整,就能够开发出一款更好用的开发者工具,同时依然能享受 Flux 架构带给你的可预测性。架构
确切的说,他想要的开发者工具包含了代码热替换(hot reload)和时间旅行(time travel)功能。然而要想在 Flux 架构上实现这些功能,确实有些麻烦。ide
在 Flux 中,store 包含了两样东西:函数
在一个 store 中同时保存这两样东西将会致使代码热替换功能出现问题。当你热替换掉 store 的代码想要看看新的状态改变逻辑是否生效时,你就丢失了 store 中保存的当前状态。此外,你还把 store 与 Flux 架构中其它组件产生关系的事件系统搞乱了。工具
解决方案ui
将这两样东西分开处理。让一个对象来保存状态,这个对象在热替换代码的时候不会受到影响。让另外一个对象包含全部改变状态的逻辑,这个对象能够被热替换由于它不用关心任何保存状态相关的事情。spa
时间旅行调试法的特性是:你能掌握状态对象的每一次变化,这样的话,你就能轻松的跳回到这个对象以前的某个状态(想象一个撤销功能)。
要实现这样的功能,每次状态改变以后,你都须要把旧的状态保存在一个数组中。可是因为 JavaScript 的对象引用特性,简单的把一个对象放进数组中并不能实现咱们须要的功能。这样作不能建立一个快照(snapshot),而只是建立了一个新的指针指向同一个对象。
因此要想实现时间旅行特性,每个状态改变的版本都须要保存在不一样的 JavaScript 对象中,这样你才不会不当心改变了某个历史版本的状态。
解决方案
当一个 action 须要 store 响应时,不要直接修改 store 中的状态,而是将状态拷贝一份并在这份拷贝的状态上作出修改。
当你在写一些调试性工具时,你但愿它们可以更加通用。一个使用该工具的用户应该能够直接引入这个工具而不须要作额外的包装或桥接。
要实现这样的特性,Flux 架构须要一个扩展点。
一个简单的例子就是日志。好比说你但愿 console.log() 每个触发的 action 同时 console.log() 这个 action 被响应完成后的状态。在 Flux 中,你只能订阅(subscribe) dispatcher 的更新和每个 store 的变更。可是这样就侵入了业务代码,这样的日志功能不是一个第三方插件可以轻易实现的。
解决方案
将这个架构的部分功能包装进其余的对象中将使得咱们的需求变得更容易实现。这些「其余对象」在架构原有的功能基础之上添加了本身的功能。你能够把这 种扩展点看作是一个加强器(enhancers)或者高阶对象(higher order objects),亦或者中间件(middleware)。
此外,使用一个树形结构来组织全部改变状态的逻辑,这样当状态发生改变的时候 store 只会触发一个事件来通知视图层(view),而这一个事件会被整棵树中的全部逻辑处理(译者注:「处理」不表明必定会改变状态,这些改变状态的逻辑本质上 是函数,函数内部会根据 action 的类型等来肯定是否对状态进行改变)。
*注意:就上述这些问题和解决方案来讲,我主要在关注开发者工具这一使用场景。实际上,对 Flux 作出的这些改变在其余场景中也很是有帮助。在上述三点以外,Flux 和 Redux 还有更多的不一样点。好比,相比于 Flux,Redux 精简了整个架构的冗余代码,而且复用 store 的逻辑变得更加简单。这里有一个 Redux 优势的列表可供参考。
那么让咱们来看看 Redux 是怎么让这些特性变为现实的。
从 Flux 演进到 Redux,整个架构中的角色发生了些许的变化。
Redux 保留了 Flux 中 action creator 的概念。每当你想要改变应用中的状态时,你就要 dispatch 一个 action,这也是惟一改变状态的方法。
就像我在这篇关于 Flux 的文章中 提到的同样,我把 action creator 看作是一个报务员(负责发电报的人,telegraph operator),你找到 action creator 告诉他你大体上想要传达什么信息,action creator 则会把这些信息格式化为一种标准的格式,以便系统中的其余部分可以理解。
与 Flux 不一样的是,Redux 中的 action creator 不会直接把 action 发送给 dispatcher,而是返回一个格式化好的 JavaScript 对象。
我把 Flux 中 store 的那一套机制描述为一种控制过分的官僚体系。你不能简单直接的修改状态,而是要求全部的状态改变都必须由 store 亲自产生,还必需要经历 action 分发那种套路。在 Redux 中,store 依然是这么的充满控制欲和官僚主义,可是又有些不同。
在 Flux 中,你能够拥有多个 store,每个 store 都有本身的统治权。每一个 store 都保存着本身对应的那部分状态,以及全部修改这些状态的逻辑。
而 Redux 中的 store 更喜欢将权力下放,事实上不得不这么作。由于在 Redux 中,你只能有一个 store……因此若是你打算像 Flux 那样让 store 彻底独立处理本身的事情,那么在 Redux 中,store 里的工做量将变得很是大。
所以,Redux 中的 store 首先会保存整个应用的全部状态,而后将「判断哪一部分状态须要改变」的任务分配下去。而以根 reducer(root reducer)为首的 reducer 们将会承担这个任务。
你可能发现这里好像没有 dispatcher 什么事。是的,虽然看起来有点儿越权,但 Redux 里的 store 已经彻底接管了 dispatcher 相关的工做。
当 store 须要知道一个 action 触发后状态须要怎么改变时,他会去询问 reducer。根 reducer 会根据状态对象的键(key)将整个状态树进行拆分,而后将拆分后的每一块子状态传到知道该怎么对这块状态进行响应的子 reducer 那里处理。
我把 reducers 看作是对复印情有独钟的白领们。他们不但愿把任何事搞砸,所以他们不会修改任何传递给他们的文件。取而代之的是,他们会对这些文件进行复印,而后在复印件上进行修改。(译者注:固然,当这些修改后的复印件定稿后,他们也不会再去修改这些复印件。)
这是 Redux 的核心思想之一。不直接修改整个应用的状态树,而是将状态树的每一部分进行拷贝并修改拷贝后的部分,而后将这些部分从新组合成一颗新的状态树。
子 reducers 会把他们建立的副本传回给根 reducer,而根 reducer 会把这些副本组合起来造成一颗新的状态树。最后根 reducer 将新的状态树传回给 store,store 再将新的状态树设为最终的状态。
若是你有一个小型应用,你可能只有一个 reducer 对整个状态树进行拷贝并做出修改。又或者你有一个超大的应用,你可能会有若干个 reducers 对整个状态树进行修改。这也是 Flux 和 Redux 的另外一处区别。在 Flux 中,store 并不须要与其余 store 产生关联,并且 store 的结构是扁平的。而在 Redux 中,reducers 是有层级结构的。这种层级结构能够有若干层,就像组件的层级结构那样。
Flux 拥有控制型视图(controller views) 和常规型视图(regular views)。控制型视图就像是一个经理同样,管理着 store 和子视图(child views)之间的通讯。
在 Redux 中,也有一个相似的概念:智能组件和木偶组件。(译者注:在最新的 Redux 文档中,它们分别叫作容器型组件 Container component 和展现型组件 Presentational component)智能组件的职责就像经理同样,可是比起 Flux 中的角色,Redux 对经理的职责有了更多的定义:
木偶组件不会直接依赖 action(译者注:即不会在木偶组件里 require
action 相关的文件),由于全部的 action 都会当作 props 传下来。这意味着木偶组件能够被任何一个逻辑不一样的 App 拿去使用。同时木偶组件也须要有必定的样式来让本身变得好看一些(固然你可让木偶组件接受某些 props 做为设置样式的变量)。
要把 store 绑定到视图上,Redux 还须要一点帮助。若是你在使用 React,那么你须要使用 react-redux。
视图绑定工做有点像为组件树服务的 IT 部门。IT 部门确保全部的组件都正确的绑定到 store 上,并处理各类技术上的细节,以确保余下层级的组件对绑定相关的操做毫无感知。
视图层绑定引入了三个概念:
<Provider>
组件: 这个组件须要包裹在整个组件树的最外层。这个组件让根组件的全部子孙组件可以轻松的使用 connect()
方法绑定 store。connect()
:这是 react-redux
提供的一个方法。若是一个组件想要响应状态的变化,就把本身做为参数传给 connect() 的结果(译者注:connect() 返回的依然是一个函数),connect() 方法会处理与 store 绑定的细节,并经过 selector 肯定该绑定 store 中哪一部分的数据。selector
:这是你本身编写的一个函数。这个函数声明了你的组件须要整个 store 中的哪一部分数据做为本身的 props。全部的 React 应用都存在一个根组件(root component)。他其实就是整个组件树最外层的那个组件,可是在 Redux 中,根组件还要承担额外的任务。
根组件承担的角色有点像企业中的高管,他将整个团队整合到一块儿来完成某项任务。他会建立 store,并告诉 store 使用哪些 reducers,并最终完成视图层的绑定。
当完成整个应用的初始化工做后,根组件的就再也不插手整个应用的运行过程了。每一次从新渲染(re-render)都没有根组件什么事,这些活儿都由根组件下面的子组件完成,固然也少不了视图层绑定的功劳。
让咱们看看上述各个部分是怎样组合成一个能够运行的应用的。
应用中的不一样部分须要在配置环节中整合到一块儿。
(1) 准备好 store。根组件会建立 store,并经过 createStore(reducer) 方法告诉 store 该使用哪一个根 reducer。与此同时,根 reducer 也经过 combineReducers() 方法组建了一只向本身汇报的 reducer 团队。
(2) 设置 store 和组件之间的通讯。根组件将它全部的子组件包裹在 <Provider>
组件中,并创建了 Provider 与 store 之间的联系。
Provider 本质上建立了一个用于更新视图组件的网络。那些智能组件经过 connect() 方法连入这个网络,以此确保他们可以获取到状态的更新。
(3) 准备好 action callback。为了让木偶组件更好的处理 action,智能组件能够用 bindActionCreators()
方法来建立 action callback。这样作以后,智能组件就能给木偶组件传入一个回调(callback)。对应的 action 会在木偶组件调用这个回调时被自动 dispatch。(译者注:使用 bindActionCreators() 使得木偶组件无需关心 action 的 type 等信息,只用调用 props 中的某个方法传入须要的参数做为 action 的 payload 便可)
如今咱们的应用已经配置完成,用户能够开始操做了。让咱们触发一个 action,看看数据是怎样流动的。
(1) 视图发出了一个 action,action creator 将这个 action 格式化并返回。
(2) 这个 action 要么被自动 dispatch(若是咱们在配置阶段使用了 bindActionCreators()),要么由视图层手动 dispatch。
(3) store 接受到这个 action 后,将当前的状态树和这个 action 传给根 reducer。
(4) 根 reducer 将整个状态树切分红一个个小块,而后将某一个小块分发给知道怎么处理这部份内容的子 reducer。
(5) 子 reducer 将传入的一小块状态树进行拷贝,而后在副本上进行修改,并最终将修改后的副本返回根 reducer。
(6) 当全部的子 reducer 返回他们修改的副本以后,根 reducer 将这些部分再次组合起来造成一颗新的状态树。而后根 reducer 将这个新的状态树交还给 store,store 再把本身的状态置为这个最新的状态树。
(7) store 告诉视图层绑定:「状态更新啦」
(8) 视图层绑定让 store 把更新的状态传过来
(9) 视图层绑定触发了一个从新渲染的操做(re-render)
这就是我所理解的 Redux,但愿对你有所帮助。