React学习之Redux

379 阅读5分钟

Redux 核心概念

Redux 官方文档对它的定义是:一个可预测的 JavaScript 应用状态管理容器。它提出的 "数据的唯一真相来源"单向数据流"纯函数 Reducers" 大大简化了前端逻辑,使得我们能够高效、可控地管理共享数据

  • action:一个普通的 JavaScript 对象,用来描述将要做什么事,而且它是触发数据变化的唯一原因,从而可以保证数据变化的可控性及可追踪性

  • reducer:处理器,根据不同的 action 处理数据,处理后的数据会被 store 重新保存

  • store:数据仓库,用于存储共享数据

个人理解的使用 Redux 更新共享数据的操作大致过程如下:

1627546319.jpg

举个栗子:

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

  1. 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
          }
      }
      
  2. reducer 函数内部通常使用 switch(...) case 语句进行类型判断

  3. reducer 函数需要是一个没有副作用的纯函数,它将有利于测试、调试、还原数据以及与 react 结合

  4. 当项目比较大而且存储的数据结构比较复杂的时候,需要对 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 创建的仓库,会包含一系列的方法来帮助我们管理数据:

  1. dispatch:分发 action 来更改仓库中的数据

  2. getState:获取仓库中当前存储的状态值

  3. replaceReducer:替换 store 当前的 reducer (仓库不变)

  4. 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: [] }