Redux 核心概念
Redux 官方文档对它的定义是:一个可预测的 JavaScript 应用状态管理容器。它提出的 "数据的唯一真相来源"
、单向数据流
、"纯函数 Reducers"
大大简化了前端逻辑,使得我们能够高效、可控地管理共享数据
-
action
:一个普通的JavaScript 对象
,用来描述将要做什么事,而且它是触发数据变化的唯一原因,从而可以保证数据变化的可控性及可追踪性 -
reducer
:处理器,根据不同的action
处理数据,处理后的数据会被store
重新保存 -
store
:数据仓库,用于存储共享数据
个人理解的使用 Redux 更新共享数据的操作大致过程如下:
举个栗子:
import {createStore} from 'redux'
const INCREASE = 'INCREASE' // 加
const DECREASE = 'DECREASE' // 减
/*
* reducer() 改变数据的函数
* @param {any} state 仓库中存储的之前的数据
* @param {Object} action 描述数据操作的对象
*/
function reducer(state, action) {
switch(action.type) {
case INCREAASE:
return state + 1
case DECREASE:
return state - 1
default:
return state
}
}
// createStore(reducer, initialState) 创建存储数据的仓库
// 先看两个参数:更新数据的函数与状态初始值
const store = createStore(reducer, 10) // 初始化状态值为 10
const action = { type: INCREASE } // +1 的 action 对象
console.log(store.getState()) // 打印:10
// 调用 store.dispatch 分发操作以改变数据
store.dispatch(action) // +1 操作
store.dispatch(action) // +1 操作
console.log(store.getState()) // 打印:12
Action
-
Action 必须是一个
plain object
(普通的平面对象),如直接用字面量创建的对象 (它的__proto__
直接指向Object.prototype
),而不是
类似任何继承形式创建的实例对象,比如:class Action { constructor(type){ this.type = type } } const action = new Action('INCREASE') // 如上面栗子中的仓库,调用 dispatch 方法会直接报错 store.dispatch(action)
-
Action 对象必须拥有
type 属性
,用于描述操作的类型,但并未对其类型进行约束 (string
,number
,symbol
等均可) -
action 创建函数
:一般通过函数创建标准的 Action 对象,而且方便统一管理// action-types.js 这里就使用 Symbol type export const INCREASE = Symbol('increase') export const DECREASE = Symbol('decrease') export const SET = Symbol('set') // number-action.js import * as actionTypes from './action-types' export function getIncreaseAction() { return { type: actionTypes.INCREASE } } export function getDecreaseAction() { return { type: actionTypes.DECREASE } } export function getSetAction(value) { return { type: actionTypes.SET, /* 通常使用 payload 字段表示附加的数据 */ payload: value } }
-
为了方便利用 action 创建函数来分发 action,redux 提供了一个函数
bindActionCreators
,用于增强 action 创建函数的功能,使它不仅可以创建 action,并且创建后可以自动完成分发 action 的功能import {bindActionCreators} from 'redux' import {getIncreaseAction, getDecreaseAction, getSetAction} from 'number-action' // 第一个参数是 action 创建函数合并的对象或者是一个 action 创建函数 // 第二个参数是仓库的 dispatch 函数 // 返回结果由传入的第一个参数决定 // 返回新函数,调用它即可自动分发 action // 返回新对象,其中每个属性名与第一个参数对象的每个属性名一致 const actionCreators = { increaseNum: getIncreaseAction, decreaseNum: getDecreaseAction, setNum: getSetAction } // store 为 createStore 创建的 Store 对象 const boundActions = bindActionCreators(actionCreators, store.dispatch) // 后续分发 action 时 // 直接调用 boundActions.getIncreaseAction() 即可分发 action // 而不需要再去手写 store.dispatch(actionTypes.getIncreaseAction())
Reducer
它是用于改变数据的函数
一个数据仓库,有且仅有一个 reducer,并且通常情况下,一个工程只有一个仓库,因此,一个系统只有一个 reducer
-
reducer 函数被调用的时机:
-
通过
store.dispatch
方法分发 action 时 -
刚刚创建仓库时,会调用 reducer 进行初始化 (所以可以利用这一点,用 reducer 函数初始化状态),此时 action 的 type 类型是一个特殊的类型:
@@redux/INITxxxxxxx
// 创建仓库时不传递默认值,则传递的是 undefined // 因而可以取 reducer 函数参数的默认值 const store = createStore(reducer) const initialState = xxx // 仓库的初始化状态值 function reducer(state = initialState, action) { switch(action.type){ // ... default: return state } }
-
-
reducer 函数内部通常使用
switch(...) case
语句进行类型判断 -
reducer 函数需要是一个没有副作用的纯函数,它将有利于测试、调试、还原数据以及与 react 结合
-
当项目比较大而且存储的数据结构比较复杂的时候,需要对 reducer 进行细分 (最终需要合并为一个 reducer)
// 比如,共享数据有登录用户信息和所有用户列表 // reducers/loginUserInfo.js import {SET_LOGIN_USER_INFO} from './loginUser-types' const initialState = null // 初始化状态值 export default (state = initialState, { type, payload }) => { switch (type) { case SET_LOGIN_USER_INFO: return payload default: return state } } // reducers/users.js import {SET_USERS} from './users-types' const initialState = [] // 初始化状态值 export default (state = initialState, { type, payload }) => { switch (type) { case SET_USERS: return payload default: return state } } // reducers/index.js 合并 reducer 后传递给 store import loginUserReducer from './loginUser' import usersReducer from './users' export default (state = {}, action) => { const newState = { loginUser: loginUserReducer(state.loginUser, action), users: usersReducer(state.users, action) } return newState }
如上代码所示,通过
手动书写合并 reducer 的函数
,从而将状态管理交给下面的 reducer 去处理,从而降低状态管理的复杂度。当然, redux 帮我们封装好了一个方法combineReducers
用于合并我们细分的 reducer,表现上与上面手写的合并函数相同// reducers/index.js 合并 reducer 后传递给 store import { combineReducers } from 'redux' import loginUserReducer from './loginUser' import usersReducer from './users' export default combineReducers({ loginUser: loginUserReducer, users: usersReducer }) // store.getState() 获取 state 的属性名,则由这个合并处理对象的属性名决定
Store
就是用于保存数据的仓库,是一个通过 createStore
创建的仓库,会包含一系列的方法来帮助我们管理数据:
-
dispatch
:分发 action 来更改仓库中的数据 -
getState
:获取仓库中当前存储的状态值 -
replaceReducer
:替换 store 当前的 reducer (仓库不变) -
subscribe
:注册一个监听器,监听器是一个无参函数,它的运行时机是在分发 action 之后
;可以注册多个监听器,且按注册的顺序触发;该函数返回一个函数,用于取消监听const store = createStore(reducer) // 返回一个函数用于取消监听 const unSubscribe = store.subscribe(() => { console.log('1. 状态改变了') }) store.subscribe(() => { console.log('2. 状态改变了') }) console.log('state: ', store.getState()) // 随意分发一个 action (举例) store.dispatch({ type: 'SetLoginUser', payload: { id: 1, username: 'K.', ... /* 其他信息 */ } }) console.log(store.getState())
上面代码的打印结果是:
'state:' { loginUser: null, users: [] } '1. 状态改变了' '2. 状态改变了' 'state:' { loginUser: { id: 1, username: 'K.', ... }, users: [] }