快速入门
本文为Redux中文文档基础部分阅读总结,建议有时间的去详尽的阅读一遍 原文
介绍
-
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
-