开篇
- 本文内容是自己在学习Redux时候,结合 Redux中文文档做的一次总结,若有不妥之处,忘加以斧正。
介绍
Redux是JavaScript状态管理器,是一个可以提供可预测化的状态管理(个人理解:可预测化的状态管理可以理解为根据已知的输入条件、能够得到固定的结果),Redux能提供一个最简化的API来使用,同时又可以做到行为的完全可预测;它体小精悍(只有2kB,包括依赖),不仅仅用于React框架、还能支持其他界面库;
核心概念
-
动机
随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态),这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的的路由,被选中的标签,是否显示加载动效或者分页器等等。管理不断变化的 state 非常困难。如果一个 model 的变化会引起另一个 model 变化,那么当 view 变化时,就可能引起对应 model 以及另一个 model 的变化,依次地,可能会引起另一个 view 的变化。直至你搞不清楚到底发生了什么。state 在什么时候,由于什么原因,如何变化已然不受控制。 当系统变得错综复杂的时候,想重现问题或者添加新功能就会变得举步维艰。
-
三大原则
-
1.单一数据源
整个应用的 state 被储存在一棵 object tree 中,(请认真且严肃的记住这一点,真正需要操作的也是state、瓶颈也会在这里),并且这个 object tree 只存在于唯一一个 store 中。
State (也称为 state tree) 是一个宽泛的概念,但是在 Redux API 中,通常是指一个唯一的 state 值,由 store 管理且由 getState() 方法获得。它表示了 Redux 应用的全部状态,通常为一个多层嵌套的对象.
有一个约定成俗的习惯就是:顶层的state为一个对象,可以是任意的数据类型;
type State = any
const initialState = { allIds: [], byIds: {} };
2.state只读
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
这样确保了视图和网络请求都不能直接修改 state;所有的修改都需要集中化处理;Action 是一个普通对象,用来表示即将改变 state 的意图。它是将数据放入 store 的唯一途径。无论是从 UI 事件、网络回调,还是其他诸如 WebSocket 之类的数据源所获得的数据,最终都会被 dispatch 成 action
有一个约定成俗的习惯:Action必须拥有一个Type域(通俗的讲就是每一个Action在type键上会定义他的对象名称,可以定义为常量,也可以是module导入),它直接告诉需要被执行的是哪一个Action,除Action之外、其他的对象有自己定义;
type Action = Object
action案例
export const addTodo = content => ({ type: SHOW_ALL, filter }); export function activeError () { return { type: ACTIVE_ERROR } }
3.使用纯函数进行修改
为了描述 action 如何改变 state tree ,你需要编写 Reducers。
Reducer 只是一些纯函数,它接收自身的state 和 action,并且返回新的state,而且是必须的需要返回;如果有多个Reducers,你可以通过Redux提供的API方法 combineReducers,将多个Reducers进行合并;
Reducer (也称为 reducing function) 函数接受两个参数:之前累积运算的结果和当前被累积的值,返回的是一个新的累积结果。该函数把一个集合归并成一个单值,如同JavaScript中的
Array.prototype.reduce()
方法一样;
type Reducer<S, A> = (state: S, action: A) => S
Reducer案例:
// 定义Reducer,接受一个自身的state和action(这里的state默认值为”SHOW_ALL“) function visibilityFilter(state = 'SHOW_ALL', action) { switch (action.type) { case 'SET_VISIBILITY_FILTER': return action.filter default: return state } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO': return [ ...state, { text: action.text, completed: false } ] case 'COMPLETE_TODO': return state.map((todo, index) => { if (index === action.index) { return Object.assign({}, todo, { completed: true }) } return todo }) default: return state } } import { combineReducers, createStore } from 'redux' let reducer = combineReducers({ visibilityFilter, todos }) let store = createStore(reducer)
-
生态系统(我这里只列举和React相关的)
-
1.不同框架绑定
- react-redux — React
2. 中间件
- redux-thunk — 用最简单的方式搭建异步 action 构造器
- redux-promise — 遵从 FSA 标准的 promise 中间件
- redux-axios-middleware — 使用 axios HTTP 客户端获取数据的 Redux 中间件
- redux-observable — Redux 的 RxJS 中间件
- redux-rx — 给 Redux 用的 RxJS 工具,包括观察变量的中间件
- redux-logger — 记录所有 Redux action 和下一次 state 的日志
- redux-immutable-state-invariant — 开发中的状态变更提醒
- redux-unhandled-action — 开发过程中,若 Action 未使 State 发生变化则发出警告
- redux-analytics — Redux middleware 分析
- redux-gen — Redux middleware 生成器
- redux-saga — Redux 应用的另一种副作用 model
- redux-action-tree — Redux 的可组合性 Cerebral-style 信号
- apollo-client — 针对 GraphQL 服务器及基于 Redux 的 UI 框架的缓存客户端
3. 更多生态系统:
- 访问网址:https://www.redux.org.cn/docs/introduction/Ecosystem.html**
基础介绍
Action
这里的Action仅仅代表给Action下定义,还不涉及到调用使用
Action
是把数据从应用(译者注:这里之所以不叫 view 是因为这些数据有可能是服务器响应,用户输入或其它非 view 的数据 )传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store。
本质上是一个JavaScript普通对象,由一个Type约定表示需要执行的动作
//Action type 常量
const ADD_TODO = 'ADD_TODO'
// 定义对象
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
// 引用
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
Action 创建函数(这里的才是生成action的方法,提供给Reduces调用)
注意“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。
// 定义addTodoAction函数;
function addTodo(text) {
return {
type: ADD_TODO,
text
}
}
通常我们在使用的时候、如何触发这个action呢,在Redux中只需把 action 创建函数的结果传给 dispatch() 方法即可发起一次 dispatch 过程;
dispatch(addTodo(text))
dispatch(completeTodo(index))
或者创建一个 被绑定的 action 创建函数 来自动 dispatch:
const boundAddTodo = text => dispatch(addTodo(text))
const boundCompleteTodo = index => dispatch(completeTodo(index))
然后直接调用
boundAddTodo(text);
boundCompleteTodo(index);
虽然说 store 里能直接通过 store.dispatch() 调用 dispatch() 方法,但是多数情况下你会使用 react-redux 提供的 connect() 帮助器来调用。bindActionCreators() 可以自动把多个 action 创建函数 绑定到 dispatch() 方法上。
####案例(action.js):
/*
* action 类型
*/
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO'
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER'
/*
* 其它的常量
*/
export const VisibilityFilters = {
SHOW_ALL: 'SHOW_ALL',
SHOW_COMPLETED: 'SHOW_COMPLETED',
SHOW_ACTIVE: 'SHOW_ACTIVE'
}
/*
* action 创建函数
*/
export function addTodo(text) {
return { type: ADD_TODO, text }
}
export function toggleTodo(index) {
return { type: TOGGLE_TODO, index }
}
export function setVisibilityFilter(filter) {
return { type: SET_VISIBILITY_FILTER, filter }
}
Reducer
Reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state,Reducer就是用来处理如何更新的。
-
在设计Reducer之前,需要确定state对象返回的结构(重要,最终所有的state都是这里返回的);
因为Reducer处理完之后是需要返回一个新的对象结果所以我们需要用最简单的形式把应用的state用对象描述出来;
-
Action处理
在确定好state对象的结构之后,就可以开发reducer了。reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state
(previousState, action) => newState
之所以将这样的函数称之为reducer,是因为这种函数与被传入 Array.prototype.reduce(reducer, ?initialValue) 里的回调函数属于相同的类型。
-
保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如 Date.now() 或 Math.random()。
在以下案例中会出现几个函数,在这里提前说明以下;
-
combineReducers(Redux API)
调用redux提供的combineReducers函数,该函数把多个reducer函数组合在一次,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理;然后这个生成的新的函数再将所有 reducer 的结果合并成一个大的对象
-
不要修改 state。 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用 { …state, …newState } 达到相同的目的。
-
在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。
-
注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。
案例(reducers.js)
// 调用combineReducers,组合多个reducers
import { combineReducers } from 'redux'
// 引入action.js
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
// 定义 visibilityFilter的 reducer函数,这里的states写入默认值初始值,
// Redux 首次执行时,state 为 undefined,此时我们可借机设置并返回应用的初始 state
function visibilityFilter(state = SHOW_ALL/** es6写法,提供默认值*/, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return action.filter
default:
return state
}
}
// 定义 todos 的reducer函数,管理自己的state 默认为【】
function todos(state = [], action) {
switch (action.type) {
case ADD_TODO:
return [
...state, // 数组对象的展开运算符,结果会合并,通Object.assgin({},state,{text:**,completed:***})
{
text: action.text,
completed: false
}
]
case TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
// 合并2个reducer函数
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
Store
在上面已经使用了action来描述发生了什么、使用reducers来根据action更新state的用法;
Store 就是把它们联系到一起的对象。Store 有以下职责:
- 维持应用的 state;
- 提供 getState() 方法获取 state;
- 提供 dispatch(action) 方法更新 state;
- 通过 subscribe(listener) 注册监听器;
- 通过 subscribe(listener) 返回的函数注销监听器。
再次强调一下 Redux 应用只有一个单一的 store。当需要拆分数据处理逻辑时,你应该使用 reducer 组合 而不是创建多个 store。
import { createStore } from 'redux'
// combineReducers() 合并之后
import todoApp from './reducers'
let store = createStore(todoApp)
// 第二个参数是可选的,用于设置 state 初始状态
// let store = createStore(todoApp, window.STATE_FROM_SERVER)
总结
以上就是对Redux的学习记录;总结如下;
-
在Redux中有三个概念 State、Action、Reducer
State
- state 为应用的状态;所有的状态最后都会存储在一颗object tree中。这个object tree存在store中;
- state 是只读;在操作的时候千万不要改变其状态,唯一能够改变state的只有action(个人考虑可能是由于满足单一数据源、单项数据流)
Action
- action唯一可以更改state,他是store唯一的数据来源,只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。并且通过action函数把数据从应用传到store 的有效载荷。:
Reducer
reducers 指定了应用状态的变化如何响应 actions 并发送到 store 的;其中每个reducer函数只负责管理全局 state 中它负责的一部分,其实也就是自己定义的部分,
需要注意几点:
- 不要修改传入参数;
- 不要执行有副作用的操作,如 API 请求和路由跳转;
- 不要调用非纯函数,如 Date.now() 或 Math.random()
- 在 default 情况下返回旧的 state
使用总结:
应用通过store.dispatch 分发action函数(“action函数”(就是生成action方法,与“action定义“是两个不同的概念)” 可以传入参数,可能在后面reducer中使用到),然后调用定义在reducer的函数(最后合并所有的reducer函数,返回一个大的集合对象,其实就是逻辑处理部分,这部分是不可见的),根据对应的action对象中的type选择不同的处理程序;最后返回自己负责的state对象,这些state会修改store中的state