Redux是受Flux启发的一种模式,导致大家对Flux感觉非常迷惑的一件事是Flux和Redux的区别。这篇文章中,我将解释两者之间的不同。
如果你还没有阅读我最近一篇关于Flux的文章,那就先看看它吧。
为什么要修改Flux?
Redux解决了和Flux一样的问题,当然还有其他。
和Flux一样,它让state的改变在app中有更强的可预测性。如果你想改变state,你就必须发送一个action。没有方法可以直接修改state,因为持有state的对象只有getter
方法,没有setter
方法。这些基本的东西两者是非常相似的。
那么为什么要用不同的模式呢? Redux 的创造者Dan Abramov说,这是一个提升Flux的机会。他想要更好的开发工具。他说如果你移动了周围的一些东西,你能让创造出更好的开发工具成为可能,但是也有和Flux给的一样的可预测性。
他想要热重载和时间旅行调试(详见另一篇文章another cartoon to explain these)。但是有些问题让开发工具很难调试Flux。
问题1: 在不清除state的情况下,Store不能重新加载
在Flux中,store包含两部分:
- State改变的逻辑
- 当前的State
将这两点放在同一个对象上则不能进行热重载。当你重载store对象来看state改变后的效果,你会失去store持有的数据。另外,你打乱了连接store到系统其他部分的事件订阅。
解决方法
拆分这两个方法。让store持有state,store将不会被重新加载。Reducer包含所有state改变的逻辑,reducer能被重新加载,因为它不需要关心持有任何state。这样就可以热重载state变化的逻辑(reducer)而不丢失store中所存储的state信息。
问题2: 任何action的触发都会重写state
在时间旅行调试中,你跟踪每个版本的state状态。这样,你可以回溯到某一个较早的状态。
每次state改变后,你需要将旧的state追加到一个保存之前state的数组中。但是因为JavaScript的工作方式,简单的添加一个变量到数组中并不工作。这不会创建这个state对象的截图,只是创建了一个新的指针指向同一个对象。
为了让它工作,每个版本都需要一个完全分离的对象,以免你不小心修改到之前的某个版本的state。
解决方法
当store中接收到一个action,不要通过改变状态来处理它。而是拷贝原来的state,然后改变这个拷贝的state。
问题3: 没有让第三方插件能能很好介入的地方
当你在开发开发工具时,你需要将它们写的更普遍一些。一个用户能够只将工具加进来,而不用去修改他们自己的代码来让它工作。
为此,你需要扩展很多点或面,能够让你的code可以添加一些新的东西到它里面。
一个例子,打印日志。 当action被触发时,你需要打印每个action,然后你还需要打印通过action改变的state。在Flux中,你不得不监听dispatcher的更新,然后去更新每个store。但是这是自定义代码,而不是通过第三方工具来简单的完成这个需求。
解决方法
包装一部分系统到另一些对象中让它变得简单。这些对象在原始对象的基础上添加它们那部分的功能。你可以通过类似enhancers
、higher order
对象或middleware
看出这类扩展的点。
另外,用树状结构去结构化state改变的逻辑。在整个state树被处理之后,store只需要触发一个事件就能通知到所有state改变了的view。
⚠️备注: 对这些问题和解决方法,我主要集中在对开发者工具这个例子。这些改变也对其他案例有帮助。除此之外,Flux和Redux之间还有其他的不同。比如,Redux拆分样本文件,这可以让store中的逻辑更容易复用。这是Redux的一些优点。
接下来让我们来看看Redux是怎么让这些成为可能的。
新特征
从Flux到Redux有一点点特征的改变。
Action creators
Redux从Flux保留了action creator。不论什么时候你想改变app的state,你都需要触发一个action。这是state唯一能被改变的方法。
如我在图解Flux所说,action creator 就好像一个电报员。当你去到action creator时,你已经基本知道你要发送的信息是什么了,然后action creator把信息按整个系统都能理解的方式格式化。
不像Flux的是,Redux中action creator不会将action发送给dispatcher。相应的,它们会返回一个格式化好的action对象。
Store
在Flux中,我将store看作一个过度控制的官僚。所有的state不是直接请求被改变,而都得通过store来改变,而且不得不通过整个action pipeline。Redux中store依然是一个过度控制的官僚,当有一点不同。
在Flux中,你可以有多个store。每个store有它自己的一点封地,它自己完全掌控。它持有它自己的一些state,也有改变这些state的逻辑。
Redux的store更喜欢让其他代理它做事。在Redux中,有且仅有一个store。因此,如果它自己控制所有的改变,它将会负载过重。
它只关心持有的整个state树,然后把计算需要做哪些改变工作代理出去。Reducer们,以根reducer为首的,就是接手这个任务的。
你也许注意到了这里没有dispatcher。那是因为在一点权利攫取中,store接管了dispatcher的任务。
Reducers
当store需要知道一个action怎样去改变state的时候,它需要询问reducer们。根reducer负责并基于state对象的keys切分state。然后将每小块state传递给知道怎样处理这块state的reducer。
我觉得reducer像一个白领,一个对复印有一点过于热情的角色。他们不想打乱任何事情,因此他们不像改变传递给它们的state。相反,他们会拷贝一份,然后在这个拷贝上做改变。
这是Redux的关键点之一。State对象是不可以直接被操作的。相反,每一块state都被拷贝,然后把所有的小块state合并为一个新的state对象。
Reducer们把它们的拷贝返回给根reducer,根reducer将所有的拷贝粘贴在一起就形成了更新后的state对象。然后根reducer再降新的state对象返回给store,store将它转化成新的真牌state。
如果你有一个小的app,你可能只有一个reducer,拷贝整个state对象,然后对它进行修改。或者你有一个很大的app,你也许有整颗树的reducer们。这是Flux和Redux的另一个不同。
在Flux中,store之间不是必须有联系的,它们是一个扁平的结构。
在Redux中,reducer们是一个层次结构。这个层次结构可以根据需要有多个层次,就和component的层次结构一样。
Views: 智能的和愚蠢的component
Flux有controller view和普通的view,controller view 扮演者中间管理者的角色,管理store和它的子view们之间的交流。
在Redux中,有一个相似的概念,愚蠢的和智能的component。智能的component是管理者。它们遵循比controller view 更多的一些规则,尽管:
- 智能component们负责action。如果一个在智能component下层的愚蠢的component需要触发一个action,智能的component会将以props的形式传递一个方法给它。愚蠢的component只需将它当作回调来处理。
- 智能的component没有它们自己的css 样式。
- 智能的component很少emit它们自己的DOM。而是安排愚蠢的component 操控DOM元素的布局。
愚蠢的component不直接依赖action文件,因为所有的action都是通过props传递给它的。这意味着愚蠢的component能够在app中有不同的逻辑的地方重用。它们也有自己的style(当然你也可以自定义style,仅仅通过接收一个prop,然后把它merge到默认的style中即可)
View层绑定
Redux需要提供一些帮助来将store和view连接到一起。它需要一些东西将两者绑定到一起。这被称为view层绑定。如果你正在使用React,这就是
react-redux
。
View层绑定就像the view tree的IT部门。它保证所有的component能够连接到store。它也负责很多技术细节,这样其他层就不用去了解它们。
View层绑定引入了三个概念:
- Provider component: 它将组件树包装起来。让根component的子组件们能够挂载到store上来使用
connect()
- connect(): 它是
react-redux
提供的一个方法。如果一个component想要更新state,它使用connect()
包装自己。然后connect方法会为它设置好所有的线路来使用selector。 - selector: 你可以写这个方法。它指明一个component需要state的哪一部分作为属性。
根组件
所有使用React的app都有根组件,即最上层的组件。但是使用Redux的app中,这个组件会负责更多的事情。
它扮演的角色就像一位高管。它将所有的团队放在一起来工作。它创建store,告诉store用哪个reducer,将view层绑定和view放在一起。
当然,根组件在初始化应用程序后基本上就放手不管了。它不需要关心触发渲染器这样的日常问题,它把这些事情都留通过view层绑定给它的子component去做。
它们怎么一起工作的
接下里让我们看看它们是怎么一起工作来创建一个功能强大的应用的。
准备
在准备阶段需要将应用的不同部分被连接到一起。
- 准备好store。 根组件通过
createStore()
创建store, 告诉它用哪一个根reducer。这个根reducer则通过combineReducers ()
来将一组reducer组合在一起。
- 建立store和component们之间的沟通。根组件通过provider组件对它的子component们进行包装,连接store和provider。
Provider创建一个基本的更新component的网络,智能的component通过connect()
连接到网络中,来确保它们得到状态更新。
- 准备action的回调方法。为了让愚蠢的component能更简单的和action一起工作,智能的component会通过
bindActionCreators ()
来准备action的回调方法。这样,它们只需要将一个回调方法传递给愚蠢的component即可。这个action会在它被格式化好后自动发送。
数据流
现在应用已经创建好了,用户可以和它进行交互。让我们触发一个action来看看数据流。
-
View请求一个action。Action creator 格式化并返回一个action。
- Action 不是自动发送(如果在准备阶段用了
bindActionCreators()
)就是通过view 发送。
-
Store接收action,它将当前的state树和action一起发送给根reducer。
-
根reducer将state树切分成很多小块。然后将每一块发送给知道怎么处理这块state的子reducer。
-
子reducer拷贝state块,然后在这个拷贝上进行修改。再将修改后的拷贝返回给根reducer。
-
一旦所有的子reducer们都返回了它们的修改后的拷贝,根reducer将所有的拷贝放在一起形成一个完整的更新后的state树,再返回给store。Store用新的state树替换掉旧的state树。
-
Store通知view层绑定有新的state来了。
-
View层绑定让store将新的state发送给它。
-
View层绑定出触发重新渲染。
这就是我看到的Redux与Flux的区别。希望对你有所帮助。
Resource:
A cartoon Intro to Redux