介绍
Redux 是 JavaScript 状态容器, 提供可预测化的状态管理.
动机:
随着 JavaScript 单页应用开发日趋复杂, JavaScript 需要管理比任何时候都要多的 state(状态). 这些 state 可能包括服务器响应, 缓存数据, 本地生成尚未持久化到服务器的数据, 也包括 UI 状态, 如激活的路由, 被选中的标签, 是否显示加载动效或者分页器等等. 管理不断变化的 state 非常困难. 如果一个 model 的变化会引起另一个 model 变化, 那么当 view 变化时, 就可能引起对应 model 以及另一个 model 的变化, 依次地, 可能会引起另一个 view 的变化.
state 在什么时候, 由于什么原因, 如何变化已然不受控制.
通过限制更新发生的时间和方式, Redux 试图让 state 的变化变得可预测 .
核心概念
用一个普通对象来描述应用的 state, 当你想要更新 state 中的数据时, 你需要发起一个 action .
Action 就是一个普通 JavaScript 对象 (注意到没, 这儿没有任何魔法?) 用来描述发生了什么 (或者说 Action 是一个指示器, 用来指示针对 state 的更新, 提出要干什么).
Reducer 是为了接受 state 和 action , 并 返回新的 state 的函数 (或者说 Reducer 是针对 Action 提出的需求, 来具体实现这一改动的函数).
总体流程就是: state 要改变, 发起一个 Action; Action 针对 state 的改变, 提出需要干什么 ; Reducer 根据 Action 提出的内容, 具体去完成这一改变, 并且返回新的 state .
三大原则
Redux 可以用这三个基本原则来描述:
单一数据源
整个应用的 state 被储存在一棵 object tree 中, 并且这个 object tree 只存在于 唯一一个 store 中.
State 是只读的
唯一改变 state 的方法就是触发 action ,action 是一个用于描述已发生事件的普通对象.
使用纯函数来执行修改
为了描述 action 如何改变 state tree , 你需要编写 reducers .reducer 只是一些 纯函数 , 它接收先前的 state 和 action, 并返回新的 state.
基础
Action
Action 是把数据从应用 (译者注: 这里之所以不叫 view 是因为这些数据有可能是服务器响应, 用户输入或其它非 view 的数据 ) 传到 store 的有效载荷. 它 是 store 数据的唯一来源 . 一般来说你会通过 store.dispatch() 将 action 传到 store.
例如, 添加新 todo 任务的 action 是这样的:
- const ADD_TODO = 'ADD_TODO'
- {
- type: ADD_TODO,
- text: 'Build my first Redux app'
- }
Action 本质上是 JavaScript 普通对象.
我们约定, action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作. 多数情况下, type 会被定义成字符串常量. 当应用规模越来越大时, 建议使用单独的模块或文件来存放 action.
例如:
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
样板文件使用提醒
使用单独的模块或文件来定义 action type 常量并不是必须的, 甚至根本不需要定义. 对于小应用来说, 使用字符串做 action type 更方便些. 不过, 在大型应用中把它们显式地定义成常量还是利大于弊的.
我们应该尽量减少在 action 中传递的数据.
Reducer
Reducers 指定了 应用状态的变化如何响应 actions 并发送到 store 的, 记住 actions 只是描述了有事情发生了这一事实, 并没有描述应用如何更新 state.
reducer 就是一个纯函数, 接收旧的 state 和 action, 返回新的 state.
通俗的讲: Reducer 就是给 Store 生产新 State 的地方.
(previousState, action) => newState
reducer 是一个纯函数, 保持 reducer 纯净非常重要. 永远不要在 reducer 里做这些操作:
修改传入参数;
执行有副作用的操作, 如 API 请求和路由跳转;
调用非纯函数, 如 Date.now() 或 Math.random().
需要谨记 reducer 一定要保持纯净.
只要传入参数相同, 返回计算得到的下一个 state 就一定相同. 没有特殊情况, 没有副作用, 没有 API 请求, 没有变量修改, 单纯执行计算.
combineReducers(reducers)
注意每个 reducer 只负责管理全局 state 中它负责的一部分. 每个 reducer 的 state 参数都不同, 分别对应它管理的那部分 state 数据.
随着应用的膨胀, 我们还可以将拆分后的 reducer 放到不同的文件中, 以保持其独立性并用于专门处理不同的数据域. 为此, Redux 提供了 combineReducers() 工具类, 将多个 reducer 管理起来, 这样可以消除一些样本模板代码.
例如:
- import { combineReducers } from 'redux'
- const todoApp = combineReducers({
- visibilityFilter,
- todos
- })
- export default todoApp
注意上面的写法和下面完全等价:
- export default function todoApp(state = {}, action) {
- return {
- visibilityFilter: visibilityFilter(state.visibilityFilter, action),
- todos: todos(state.todos, action)
- }
- }
combineReducers() 所做的只是生成一个函数, 这个函数来调用你的一系列 reducer, 每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理, 然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象. 如果 combineReducers() 中包含的所有 reducers 都没有更改 state, 那么也就不会创建一个新的对象.
Store
我们已经知道了 action 用来描述 "发生了什么";reducers 根据 action 更新 state .Store 就是把它们联系到一起的对象.
Store 有以下职责:
维持应用的 state;
提供 getState() 方法获取 state;
提供 dispatch(action) 方法更新 state;
通过
subscribe(listener)
注册监听器;
通过
subscribe(listener)
返回的函数注销监听器.
再次强调一下 Redux 应用只有一个单一的 store. 当需要拆分数据处理逻辑时, 你应该使用 reducer 组合 而不是创建多个 store.
创建 store:
根据已有的 reducer 来创建 store 是非常容易的. 前面我们使用 combineReducers() 将多个 reducer 合并成为一个 reducer(todoApp). 现在我们将其导入, 并传递 createStore().
- import { createStore } from 'redux'
- import todoApp from './reducers'
- let store = createStore(todoApp)
数据在 Redux 应用中的流动
严格的单向数据流是 Redux 架构的设计核心. 这意味着应用中所有的数据都遵循相同的生命周期, 这样可以让应用变得更加可预测且容易理解.
Redux 应用中数据的生命周期遵循下面 4 个步骤:
调用
- store.dispatch(action)
- .
Action 就是一个描述 "发生了什么" 的普通对象. 比如:
- { type: 'LIKE_ARTICLE', articleId: 42 }
- { type: 'FETCH_USER_SUCCESS', response: { id: 3, name: 'Mary' } }
- { type: 'ADD_TODO', text: 'Read the Redux docs.' }
你可以在任何地方调用
store.dispatch(action)
, 包括组件中, XHR 回调中, 甚至定时器中.
Redux store 调用传入的 reducer 函数.
Store 会把两个参数传入 reducer: 当前的 state 树和 action. 例如, 在这个 todo 应用中, 根 reducer 可能接收这样的数据:
- // 当前应用的 state(todos 列表和选中的过滤器)
- let previousState = {
- visibleTodoFilter: 'SHOW_ALL',
- todos: [
- {
- text: 'Read the docs.',
- complete: false
- }
- ]
- }
- // 将要执行的 action(添加一个 todo)
- let action = {
- type: 'ADD_TODO',
- text: 'Understand the flow.'
- }
- // reducer 返回处理后的应用状态
- let nextState = todoApp(previousState, action) // 当前 state,action
根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树.
Redux 原生提供 combineReducers() 辅助函数, 来把根 reducer 拆分成多个函数, 用于分别处理 state 树的一个分支. 例如:
- function todos(state = [], action) {
- // 省略处理逻辑...
- return nextState
- }
- function visibleTodoFilter(state = 'SHOW_ALL', action) {
- // 省略处理逻辑...
- return nextState
- }
let todoApp = combineReducers({ //combineReducers()将多个 reducer 整合
- todos,
- visibleTodoFilter
- })
Redux store 保存了根 reducer 返回的完整 state 树.
这个新的树就是应用的下一个 state! 所有订阅
store.subscribe(listener)
的监听器都将被调用; 监听器里可以调用 store.getState() 获得当前 state.
现在, 可以应用新的 state 来更新 UI. 如果你使用了 React Redux 这类的绑定库, 这时就应该调用
component.setState(newState)
来更新.
总结:
在写编程中, 这个 Store 一般是见不到它的身影. 更多的是和 reducer,state,dispach action 打交道. 所以 React-Redux 的工作流程 就变成了这样:
reducer ----> state ---> store ----> Component ----> UI ----> action ----> dipatch(action) ---> store -----> reducer
由由于 store 不常见, 所以可以简单粗暴理解为:
reducer ----> state ----> Component ----> UI ----> action ----> dipatch(action) -----> reducer
关注下面的标签, 发现更多相似文章
- React.js
- Redux
- JavaScript
前端
掘金招聘前端开发, 后端开发, 运营经理, 内容运营
加入掘金和开发者一起成长. 发送简历到 hr@xitu.io, 期待你的加入!
评论
登录
说说你的看法
来源: https://juejin.im/post/5af40c326fb9a07ac4800b8d