1. 动机
解决复杂状态(State)难以管理的问题。试图让state的变化变得可预测。
2. 核心
一个应用一般需要有一个数据state, rudux 中更新state中的数据,需要发起一个acton(action 是一个普通的javascrpt对象,用来描述发生了什么,强制使用action可以清晰的知道应用中到底发生了什么). redux中通过reduce把 action 和 state 串起来(reducer是一个接收state和action,并返回新的state的函数)。一般会有一个专门的reducer函数来管理调用所有的reducer,从而来管理整个应用的state. 这差不都是Redux思想的全部。(但主要的想法是如何根据这些action对象来更新state)
3. 三大原则
1) 单一数据源
整个应用的state被存储在一棵object tree中, 并且这个object tree 只存在于唯一一个store中。
(来自服务端的state可以在无需板鞋更多代码的情况下被序列化并注入到客户端中有益于单一的state tree,以前难以实现的如“撤销/重做”这类功能也变得轻而易举)
2) State是只读的
唯一改变state的方法几十触发action, action 是一个用于描述已发生事件的普通对象。
3) 使用纯函数来执行修改
为了描述action如何改变 state tree , 你需要编写reducers. reducer只是函数,它接收先前的state和action,并返回新的state(会用新的state来更新旧的state).
(随着应用的变大,剋吧reducer拆成多个小的reducers,分别独立地操作state tree 的不同部分)
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)
至此应该明白 Redux 是怎么回事了。
4. Redux是一个体校精悍的库, 大概2KB。
1. Action (携带数据及描述操作行为的数据对象)
把数据从应用传到store的有效载荷。 它是store数据的唯一来源。一般会通过store.dispatch()将action传到store.
(多数情况下会使用react-redux提供的connect() 帮助器来调用。bindActionCreators()可以自动把多个action创建函数绑定到dispatch()方法上)
注:
1. redux约定,action内必须使用一个字符串类型的type字段来表示将要执行的动作(当规模越来越大时,建议使用单独的模块或文件来存放action)。除了type字段外,action对象完全由你自己决定(应尽量减少在action中传递的数据)。
2. action创建函数就是生成action的一个方法。 action 和 action创建函数 这个两个概念很容易混在一起,使用时最好注意区分。
源码:
/*
* 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 }
2. Reducer (接收action对象, 通过action的描述,执行需要的数据操作,并返回更新后的store数据)
Reducers 指定了应用状态的变化如何响应actions,并发送到store. 记住actions只是描述了有事情发生了这一事实,并买有描述应用如何更新state.
reducer 是一个纯函数,接收旧的state和action, 并返回新的state.
保持reducer纯净非常重要,永远不要再reducer里做如下操作:
1) 修改传入的参数;
2) 执行有副作用的操作,如API请求和路由跳转;
3) 调用非纯函数, 如 Date.now() 或 Math.random();
4) 不要直接修改 state. 可以使用Object.assign() 新建一个副本;
5) 如果没有匹配任何action或更新操作,在default 情况下请返回旧的 state;
注: 每个reducer 只负责管理全局state中它负责的一部分。 每个reducer的state 参数都不同,分别对应它管理的那部分state数据。
最后, Redux提供了 combineReducers()工具类。 它所做的只是生成一个函数,这个函数来调用我们的一系列reducer, 每个reducer根据它们的key来筛选出state中的一部分数据并处理,然后这个生成的函数再将所有reducer的结果合并成一个大的对象。(如果combineReducers()中包含的所有reducers都没有更改state, 那么也就不会创建一个新的对象)
Reducer源码:
import { combineReducers } from 'redux'
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
const { SHOW_ALL } = VisibilityFilters
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 TOGGLE_TODO:
return state.map((todo, index) => {
if (index === action.index) {
return Object.assign({}, todo, {
completed: !todo.completed
})
}
return todo
})
default:
return state
}
}
const todoApp = combineReducers({
visibilityFilter,
todos
})
export default todoApp
3. Store (这里通过redux 内置的crateStore将一系列收集到的reducer更新返回的数据,聚合成状态树据。并提供一些辅助方法:getState, dispatch, ...... )
前面的亮点我们介绍了使用action来描述“发生了什么”, 和使用reducers 来根据action更新state的用法。
Store 就是把他们联系到一起的对象。Store有以下职责:
1) 维持应用的 state;
2) 提供 getState() 方法获取state;
3) 提供 dispatch(listener) 方法更新state;
4) 通过 subscribe(listerner) 注册监听器 ;
5) 通过subscribe(listener) 返回的函数注销监听器;
再次说明: Redux 应用只有一个单一的store. 当需要拆分数据处理逻辑时,应该使用reducer组合而不是创建多个store.
前面讲过,使用combineReducers() 将多个reducer合并成一个。 现在我们将其导入,并传递createStore()。
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
createStore() 的第二个参数是可以选择的, 用于设置state初始状态。(这对于开发同构应用时非常有用,服务器端redux应用的state结构可以与客户端保持一致,那么客户端可以将从网络接收到的服务端state直接用于本地数据初始化。)
let store = createStore(todoApp, window.STATE_FROM_SERVER)
发起Actions 一个参考实例:
import {
addTodo,
toggleTodo,
setVisibilityFilter,
VisibilityFilters
} from './actions'
// 打印初始状态
console.log(store.getState())
// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
const unsubscribe = store.subscribe(() => console.log(store.getState()))
// 发起一系列 action
store.dispatch(addTodo('Learn about actions'))
store.dispatch(addTodo('Learn about reducers'))
store.dispatch(addTodo('Learn about store'))
store.dispatch(toggleTodo(0))
store.dispatch(toggleTodo(1))
store.dispatch(setVisibilityFilter(VisibilityFilters.SHOW_COMPLETED))
// 停止监听 state 更新
unsubscribe()
Store 源码:
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
4. 数据流
严格的单项数据流是Redux架构的设计核心(可以避免使用多个对立的无法相互引用的重复数据)
Redux应用中的数据的生命周期遵循下面4个步骤:
1) store.dispatch(action)
可以在任何地方调用store.dispatch(action), 包括组件中, XHR回调中, 甚至定时器中。
* Redux store 调用传入的reducer函数(store 会把两个参数传入reducer: 当前的state树和action)。
// 当前应用的 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)
* 根reducer 应该把多个子reducer输出合并成一个单一的state树。
Redux原生提供combineReducers() 辅助函数,来把根reducer拆分成多个函数,用于分布处理state树的一个分支。
下面演示 combineReducers() 如何使用:
function todos(state = [], action) {
// 省略处理逻辑...
return nextState
}
function visibleTodoFilter(state = 'SHOW_ALL', action) {
// 省略处理逻辑...
return nextState
}
let todoApp = combineReducers({
todos,
visibleTodoFilter
})
当触发action后, cobineReducers 返回的 todoApp 会负责调用两个reducer:
let nextTodos = todos(state.todos, action)
let nextVisibleTodoFilter = visibleTodoFilter(state.visibleTodoFilter, action)
然后会把两个结果集合并成一个state树:
return {
todos: nextTodos,
visibleTodoFilter: nextVisibleTodoFilter
}
虽然 combineReducers() 是一个很方便的辅助工具,你也可以选择不用;可以自行实现自己的根reducer
* Redux store 保存了根reducer 返回的完整state树。
这个新的树就是应用的下一个state. 所有订阅 store.subscribe(listener) 的监听器都将被调用;监听器里可以调用 store.getStaet() 获得当前state.
现在可以应用新的state来更新UI。 如果使用了React Redux 这类的绑定库, 这时就应该调用component.setState(newState)来更新。
未完待续.....