-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Redux 入门摘要 #32
Comments
API 探索Redux API1.
|
Redux 入门介绍
Redux 是一个给JavaScript app使用的可预测的状态容器。
为什么需要Redux?(动机)
JavaScript单页应用越来越复杂,代码必须管理远比以前多的状态(state)。这个状态包括服务端返回数据,缓存数据,本地创建的数据(未同步到服务器);也包括UI状态,如需要管理激活的路由,选中的标签,是否显示加载动效或者分页器等等。
管理不断变化的状态是很难的。如果一个 model 可以更新另一个 model ,那么一个 view 也可以更新一个 model 并导致另一个 model 更新,然后相应地,可能导致另一个 view 更新 —— 你理不清你的 app 发生了什么,失去了对 state 什么时候,为什么,怎么变化的控制 。当系统变得 不透明和不确定,就很难去重现 bug 和增加 feature 了。
通过 限制何时以及怎么更新,Redux 试图让 state 的变化可以预测 。
这里可以配合阅读 You Might Not Need Redux : Redux 的引入并不一定改善开发体验,必须权衡它的限制与好处。
Redux本身很简单,我们下面首先阐述它的核心概念和三大原则。
核心概念
想象一下用普通 JavaScript 对象 来描述 app 的 state:
这个对象就像没有 setter 的 model,所以其它部分的代码不能随意修改它而造成难以复现的 bug 。
如果要改变 state ,我们必须 dispatch 一个 action。action 是描述发生了什么的普通 JavaScript 对象。
强制 每个 change 都必须用 action 来描述,可以让我们清楚 app 里正在发生什么, state 是为什么改变的。最后,把 state 和 actions 联结起来,我们需要 reducer 。
reducer 就是函数,以之前的 state 和 action 为参数,返回新的 state :
以上就是 Redux 的核心概念,注意到我们并没有用任何 Redux 的 API,没加入任何 魔法。 Redux 里有一些工具来简化这种模式,但是主要的想法是描述如何根据这些 action 对象来更新 state。
三大原则
1. 单一数据源
整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
2. State 是只读的
改变 state 的唯一方式是触发 (emit) action,action 是描述发生了什么的对象。
这确保了视图和网络请求等都不能直接修改 state,相反它们只能表达想要修改的意图。因为所有的修改都被集中化处理,且严格按照一个接一个的顺序执行,因此不用担心 race condition 的出现。
3. 使用纯函数来执行修改
为描述 action 怎么改变 state tree,你要编写 reducers。
Reducer 只是一些纯函数,它接收之前的 state 和 action,并返回新的 state。刚开始你可能只需要一个 reducer ,但随着应用变大,你会需要拆分 reducer 。
以 todo app 为例迅速上手 Redux
1. 定义 actions
Action 就是把数据从应用(这些数据有可能是服务器响应,用户输入或其它非 view 的数据)发送到 store 的有效载荷。 它是 store 数据的唯一来源,你通过
store.dispatch(action)
来发送它到 store。添加新 todo 任务的 action 是这样的:
Action 本质上是 JavaScript 普通对象。Action 必须有一个字符串类型的
type
字段来表示将要执行的动作。多数情况下,type
会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块/文件来存放 action。除了
type
字段外,action 对象的结构完全由你自己决定。但通常,我们希望减少 action 中传递的数据。Action 创建函数 (action creator)
Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。
2. Reducers
Action 只是描述了有事情发生了这一事实,并没有指明应用如何更新 state。而这正是 reducer 要做的事情。
设计 State 结构
在 Redux 应用中,所有的 state 都被保存在一个单一对象中。最好可以在写代码之前想好 state tree 应该是什么形状的。
通常,这个 state tree 需要存放一些数据,以及一些 UI 相关的 state。这样做没问题,但尽量把数据与 UI 相关的 state 分开。
处理 Action
有了 state 结构后,我们可以来写 reducer 了。 reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。
保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:
Date.now()
或Math.random()
。在高级篇里会介绍如何执行有副作用的操作。现在只需要记住 reducer 一定要保持纯净。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
注意:
Object.assign({}, ...)
新建了一个副本。default
情况下返回旧的 state。 遇到未知的 action 时,一定要返回旧的 state。我们看到,多个 action 下,reducer 开始变得复杂。是否可以更通俗易懂?这里的
todos
和visibilityFilter
的更新看起来是相互独立的,我们可以尝试拆分到单独的函数里。注意 todos 依旧接收 state,但它变成了一个数组!现在 todoApp 只把需要更新的一部分 state 传给 todos 函数,todos 函数自己确定如何更新这部分数据。这就是所谓的 reducer 合成,它是开发 Redux 应用最基础的模式。
现在更进一步,把
visibilityFilter
独立出去。那么我们可以有个主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据,然后再把这些数据合成一个大的单一对象。主 reducer 不再需要知道完整的 initial state。初始时,如果传入undefined
, 子 reducer 将负责返回它们(负责部分)的默认值。注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。
当应用越来越复杂,我们还可以将拆分后的 reducer 放到不同的文件中, 以保持其独立性并用于专门处理不同的数据域。
最后,Redux 提供了
combineReducers()
工具来做上面 todoApp 做的事情。可以用它这样重构 todoApp:3. 创建 store
前面两小节中,我们学会了使用 action 来描述“发生了什么”,和使用 reducers 来根据 action 更新 state 的用法。
Store 就是把它们联系到一起的对象。Store 有以下职责:
getState()
方法获取 state;dispatch(action)
方法更新 state;subscribe(listener)
注册监听器;subscribe(listener)
返回的函数注销监听器。再次强调一下 Redux 应用只有一个 单一 的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。
根据已有的 reducer 来创建 store 是非常容易的。在前面我们使用
combineReducers()
将多个 reducer 合并成为一个。现在我们将其导入,并传给createStore()
。你可以把初始状态 intialState 作为第二个参数传给
createStore()
。这对开发同构应用时非常有用,服务器端 redux 应用的 state 结构可以与客户端保持一致, 那么客户端可以将从网络接收到的服务端 state 直接用于本地数据初始化。发起 actions
现在我们已经创建好了 store ,可以验证一下:
4. 数据流
严格的单向数据流 是 Redux 架构的设计核心。
这意味着应用中所有的数据都遵循相同的生命周期,这样可以让应用变得更加可预测且容易理解。同时也鼓励做数据范式化,这样可以避免使用多个且独立的无法相互引用的重复数据。
Redux 应用中数据的生命周期遵循下面 4 个步骤:
调用
store.dispatch(action)
Redux store 调用传入的 reducer 函数。
根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树。
Redux store 保存了根 reducer 返回的完整 state 树。
这个新的树就是应用的下一个 state!所有订阅
store.subscribe(listener)
的监听器都将被调用;监听器里可以调用store.getState()
获得当前 state。搭配 React 一起使用
首先强调一下:Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript。
尽管如此,Redux 还是和 React 和 Deku 这类框架搭配起来用最好,因为这类框架允许你以 state 函数的形式来描述界面,Redux 通过 action 的形式来发起 state 变化。
安装 react-redux
Redux 自身并不包含对 React 的绑定库,我们需要单独安装
react-redux
。Presentational and Container Components
绑定库是基于 容器组件和展示组件相分离 的开发思想。建议先读完这篇文章。
技术上讲,我们可以手动用
store.subscribe()
来编写容器组件,但这就无法使用 React Redux 做的大量性能优化了。一般使用 React Redux 的connect()
方法来生成容器组件。(不必为了性能而手动实现shouldComponentUpdate
方法)设计组件层次结构
还记得前面 设计 state 根对象的结构 吗?现在就要定义与它匹配的界面的层次结构。这不是 Redux 相关的工作,React 开发思想在这方面解释的非常棒。
展示组件: 纯粹的UI组件,定义外观而不关心数据怎么来,怎么变。传入什么就渲染什么。
容器组件: 把展示组件连接到 Redux。监听 Redux store 变化并处理如何过滤出要显示的数据。
其它组件 有时很难分清到底该使用容器组件还是展示组件,并且组件并不复杂,这时可以混合使用。
实现组件
省略其它部分,主要讲讲容器组件一般怎么写。
connect
本身还是很明确的,指定我们注入哪些 data 和 function 到展示组件的 props ,给展示组件使用。The text was updated successfully, but these errors were encountered: