尝试理解Redux
最近在 Redux,光听别人讲总觉得半懂不懂,也很难记住,干脆自己试着写篇东西梳理一下。但在学习 Redux 之前,我们有必要明白 Redux 是什么、有什么用。官方对于 Redux 的定义是个不错的入手点。
Redux 是一个 JavaScript 应用的可预测的状态容器。
乍看这句话还挺难懂的,我们可以把这句话拆开来理解。这句话其实是对 Redux 的作用做了个总结。换句话说,我们可以把这个定义看作是官方对于这个问题 的解答:
Redux 要(尝试)解决谁的什么问题?它是怎么(尝试)去解决的?
- 谁的问题 —— JavaScript 应用
- 什么问题 —— 状态(管理)
- 怎么解决 —— (状态)可预测、容器
JavaScript 应用
Javascript 应用这个概念,写过 console.log('Hello World')
的应该都理解是什么意思,所以它的含义我就不赘述了。但值得注意的是,这里的 Javascript 应用包括且不限于 React 应用,也就是说 Redux 不止可以和 React 合用,还可以跟其他框架、库甚至原生 JS 合用。
状态(管理)
首先,什么是状态?各位学过 React 的应该都知道 State 和 Props 的概念。然而尽管这两个词看起来好像挺熟悉,但要是真的让你解释二者的区别,似乎也不太容易。这里不妨把 Props 和 State 做个对比,以便理解。
先来看几句话。
一句话概括,props 是组件对外的接口,state 是组件对内的接口。
—— 徐超《React 进阶之路》React 组件中的 props 是其父组件传入的变量。而 state 也是变量,不过是直接在组件内初始化而且受组件自身管理。
—— Flavio Copes“props”(property 的简写)是一个外界任意输入的对象,被 React 函数组件作为第一个参数接收。“state”是随着某一 React 组件的生命周期变化的数据。
—— Kent C Dodds用 state 储存你当前的页面在控制器-视图中所需的数据;用 props 把数据和事件监听器向下传入你的子组件。
可见,Props 通常与父子组件的沟通有关,而 state 与组件自身有关。React 官方文档中当然也有介绍 props 和 state,在讲解这两个概念时,文档并没有单独去分别解释 props 和 state 是什么意思,而是各自搭配了一个概念。props 搭配 component,state 则搭配 lifecycle。这样,两者的关系就清楚了。
先说状态:
- 状态就是组件自身所需的、储存在组件内部的数据。
- 状态是可变的,但只能在组件内部进行修改而不能从外部被修改。
- 状态会随着某一组件实例的生命周期不断变化。
- 与 props 相比性能较差。
另一方面,props 是这样的:
- props 通常是外界(父组件、用户等)传入的数据。
- 不可变。这一点使 React 能够快速检查引用(reference)。
- 性能较好。所以被作为一般父组件向子组件传入沟通数据和事件监听器的方式。
理解了 state 和 props 的各自的含义和区别,我们自然可以问出这个问题。
什么样的组件应该使用状态呢?
包含负责响应用户输入、服务器请求逻辑的组件。其实应用中的大部分组件只需要接收、渲染数据就可以了,所以很多开发者都倾向于把这部分组件写成无状态的函数组件,而把状态用于内部包含逻辑的少数几个组件里,这样也可以简化代码的理解难度。状态里储存的数据应该是随着用户交互、服务器请求等事件变化从而引发 UI 变更的数据。
再接下来,状态管理为什么会成为问题?
要回答这个问题我们还是应该从状态变化的原因上来找答案。
在现在前端工程中,一个应用,尤其是一个单页应用的状态很容易就变得非常复杂。传统架构中的状态数据的流动是在视图(view)和模型(model)或视图与视图之间直接进行的。状态中的一部分会通过 Ajax 或者 Websocket 到达前端,而很多状态跟后端服务器并没有关系。很多状态会在各个组件中共享、传递(比如 MVC 架构)。组件间的复杂关系导致应用状态数据支离破碎,缺少一个统一的分配方式。另一个问题是状态在组件之间的多层渗透会导致渲染成本增加、效率下降,从而影响用户体验。
可预测
跟随着 Flux、CQRS 和事件溯源的脚步,Redux 尝试通过在状态变更的时间和方式上强制施加一些限制,从而实现状态变更的可预测。
我们把前文所说的那些问题综合起来看就是 Redux 官方文档中说的状态变更时间和方式的不可预测性。如果能把状态数据何时改变和如何改变固定起来就好了。为了做到这一点, Redux 主要借鉴了 Facebook 在 2014 年推出的 Flux 架构。
What the hell is Flux?
首先说明,Flux 不是框架,而是一种架构思想。但是因为 ,很容易出现前文提到过的那些问题,所以 Facebook 放弃了 MVC 架构,转而使用 Flux。实际上,Flux 是和 React 同时出现的,而 React 自己也采用了 Flux。Flux 最大特点就是数据的单向流动。它在模型和视图之间设置了一条单向轨道(如下)。
1 | View |
或者你可以按照这张图理解:
数据如果要从模型到达视图就必须经过 action、dispatcher、store 这几个部分。这条轨道是如何实现的呢?
Flux 中一切数据流动的起点是action
(行动)。action
是什么?就是一个对象而已。它有什么用呢?Flux 就是依靠识别不同的action
决定该如何变更状态数据的,以此来固定状态变更的时间和方式。因此你可以把action
理解成命令、指令。
1 | { |
根据 Facebook 官方的 Flux 标准 Action,一个action
对象必须是一个普通的 Javascript 对象,且必须包含一个名为type
(类型)的属性,这个属性必须是一个普通的字符串,而这个type
就是用来供 Flux 识别的action
身份的。怎么识别呢?之后我们讲到 dispatcher 时再说。action
还有一个重要属性:payload
(其实还有两个可选属性error
、meta
,这里我们只讲payload
)其中包含了某些状态变更函数所需的新数据。收到不同的 action 就会对应执行不同的状态变更的。
- 一个对象:全局状态树让需要主动取用状态数据的组件可以直接访问到数据源,而不需要组件间数据的传递。
- 多个属性:状态数据
- 多个方法:
- :返回属性中的状态数据
- emitChange:
- changeData:实现数据变更所需的 callback 函数(How),之后每个 callback 会对应一个 action。
- emitChange:通过 emitter 提交变更的函数。
Dispatcher(分发器)
dispatcher.register: 参数是一个以 action 为参数的回调函数,这个函数用一个 switch 语句把所有可能用到的 store 中 callback 的调用规则按照不同的 action 类型组织起来。这个过程被称为 registration。可见 dispatcher 是任务分配的中心,你可以把它理解成十字路口的红绿灯。函数最后会调用 store 的 emitChange 方法,通知组件上的变化监听器,之后组件会做出相应的更新动作。dispatcher.dispatch 以 action 为参数,返回 store 中对应的 callback,之后直接可以拿到组建上使用。this.on(‘change’, ListStore.getAll()); 监听 dispatched action 引发的 store 的 emitChange 函数