前端学习日志:Redux

114 阅读6分钟

Redux介绍

为何使用 Redux?

Redux 帮助 JS 管理维护多种多样时刻变化的 state(状态)。

Redux 核心思想

用 action 描述状态改变,将 action 和 state 结合形成的 reducer 函数管理状态state。

state 描述应用的状态,一般用对象 {} 表示,里面的每个属性对表示应用的一个状态。

想更新 state 对象中的数据,需要发起一个 action(行为),action 就是一个普通的 JS 对象,用来描述 state 要发生的改变。 action 实例:{type:'ADD_AGE',age:30} ,该 action 的行为种类由 type 描述,为“增加年龄”;age 为一些其他属性。强制使用 action 来描述所有状态变化的好处是可以清晰地知道应用的状态发生了什么改变(由于 type)。

把状态 state 和改变状态的 action 结合起来,形成的函数就是 reducer。reducer 是一个接收 state 和 action,并返回新的 state 的函数。

Redux 三大原则

单一数据源

整个应用的状态 state 通过状态提升,被统一管理,形成一个 state 对象树(object tree),该对象树只存在于唯一一个 store 中。

state 只读

唯一改变 state 的方法就是触发 action,其他任何都不能直接修改 state。

使用纯函数来执行修改/reducer是纯函数

reducer 是纯函数,接收 state 和 action,并返回新的 state。刚开始可以只有一个 reducer,随着应用变大,可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分。因为 reducer 是函数,可以控制它们被调用的顺序,传入附加数据。

Redux 基础

State 状态树

由于单一数据源这一原则,我们要学会设计 state 对象树(object tree)。举例:

<body> 
    <div id='title'></div> 
    <div id='content'></div> 
</body>

编写出的对象树为:

const appState = {
    title: {
        text: "div1",
        color: "red"
    },
    content: {
        text: "div2",
        color: "blue"
    }
}

Dispatch

dispatch函数专门负责数据的修改,所有对数据的操作必须通过 dispatch 函数。它是 store 的一个方法,接受一个参数 action

Action

Action 本质上是 JavaScript 对象。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作。多数情况下,type 会被定义成字符串常量。如: const ADD_TODO = 'ADD_TODO' action实例:

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

应该尽量减少在 action 中除 type 外传递的数据。

Action 创建函数

action创建函数不等于action。在 Redux 中的 action 创建函数只是简单的返回一个 action,如:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

Reducer

reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state。

纯函数 一个函数的返回结果只依赖于它的参数,并且在执行过程里面没有副作用,就把这个函数叫做纯函数。简单来说,返回结果只由参数运算得到,不修改函数外的数据。

为了保持 reducer 作为纯函数,不要做以下操作(有副作用):

  • 修改传入参数;
  • 执行有副作用的操作,如 API 请求和路由跳转;
  • 调用非纯函数,如 Date.now() 或 Math.random()

编写 reducer 函数

  1. 编写初始 state,const initialState = {...};仅仅编写了,并没有初始化 state。
  2. 初始化 state,先写出不处理任何action的简单框架。
function reducerName(state = initialState, action) {
  // 这里暂不处理任何 action,
  // 仅返回传入的 state。
  return state
}

函数 todoApp 就是一个 reducer ,我们把本将写在函数体内的 if (typeof state === 'undefined') { return initialState }初始化语句写成了更为精简的表示在参数中的state = initialState。Redux 首次执行时,state 为 undefined,此时我们可借机设置并返回应用的初始 state。

  1. 添加处理 action 的代码。使用switch...case结构。
switch (action.type) {
    case xxx:
          return ...
    default:
          return state
  }

第一个 return 处理后的新 state,由于 redux 是纯函数,不能直接修改 state,要新建 state 对象副本进行修改。

  • 方法一 使用 Object.assign()新建一个副本。如 Object.assign({},state, { stateName: newStateValue }),必须把第一个参数设置为空对象,否则会改变原来的state。
  • 方法二 obj1 = { ...obj } 新建一个对象,obj1 然后把 obj 所有的属性都复制到其里面,相当于对象的浅复制。obj1和obj内容完全相同,却是两个不同的对象。除了浅复制对象,还可以覆盖、拓展对象属性。如:{...obj , stateName: newStateValue}

修改数组中指定的数据项,做法是在创建一个新的数组后, 将那些无需修改的项原封不动移入, 接着对需修改的项用新生成的对象替换。

在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。

拆分reducer函数

当有很多 action 行为,只有一个 reducer 来处理的话代码体太冗长。由此采取 reducer 合成模式,它是开发 Redux 应用最基础的模式。

开发一个函数来做主 reducer,它调用多个子 reducer 分别处理 state 中的一部分数据(处理不同的 action),然后再把这些数据返回给主 reducer,合成一个大的单一对象。主 reducer 不需要设置初始化时完整的 state。初始时,如果传入 undefined, 子 reducer 将负责返回它们的默认值。

注意每个 reducer 只负责管理 state 中它负责的一部分。每个 reducer 的 state 参数都不同,分别对应它管理的那部分 state 数据。

实例:

export default function mainReducer(state = {}, action) {
  return {
    state1: childReducer1(state.state1, action),
    state2: childReducer2(state.state2, action)
  }
}

Redux 提供了 combineReducers()工具类来简化代码。重构主 reducer:

import { combineReducers } from 'redux'

const mainReducer = combineReducers({
  childReducer1,
  childReducer2
})

export default todoApp

Store

action 用来来描述“ state 发生了什么”,reducer 用来根据 action 更新 state。 store 就是把它们联系到一起的对象。 store 的职责:

  • 维护 state
  • 提供 getState() 方法获取 state
  • 提供 dispatch(action) 方法更新 state
  • 通过 subscribe(listener) 方法注册监听器并返回的函数注销监听器。

一个 redux 应用只有一个 store。

构建store

构建 reducer。将主 reducer 导入,并传递 createStore()

import { createStore } from 'redux'
import mianReducer from './reducers'
let store = createStore(mianReducer)

这样就构建好了 store。

createStore()的结构:

function createStore (reducer) {  //参数为主reducer
  const listeners = []  //监听器数组,存放设置的监听器
  const subscribe = (listener) => listeners.push(listener)
  //subsricbe 设置监听器函数,对外是个接口,传入参数监听器(设置的操作函数),内部把传入的监听器放入数组中
  const getState = () => state
  //getState 获取状态函数
  //修改状态函数,传入参数 reducer
  const dispatch = (reducer(state,action)) => {
    state = reducer(state,action) // 覆盖原对象
    listeners.forEach((listener) => listener())
    //遍历监听器数组,执行监听器
  }
  return { getState, dispatch, subscribe }
  //对外开发三个函数接口
}

使用 store

  • 打印初始状态。 console.log(store.getState())
  • 给store注册监听器。

注册监听器原因:通常我们在用 dispatch 修改 state 时,要重新渲染界面使得同步变化(因为 dispatch 函数仅仅修改 state 对象数据),但是每次手动调用渲染操作函数十分麻烦也容易遗漏,所以要给 dispatch 注册监听器,每次 state 更新时自动调用我们想要的操作(可以是任何,如渲染组件、打印状态...)

// 每次 state 更新时,打印日志
// 注意 subscribe() 返回一个函数用来注销监听器
const unsubscribe = store.subscribe(() =>
  console.log(store.getState())
)

如上使每次state更新时自动调用store.getState()。

  • 发起 action。向 dispatch 里传参 reducer。 store.dispatch(reducerName('Learn about actions'))
  • 停止监听 state 更新。unsubscribe();

Redux 总结

  1. 构建 state 对象树
  2. 设置 action 和 reducer 修改 statefunction reducer (state, action) { /* 初始化 state 和 switch case */ }。这里可以构建一个主 reducer ,调用很多子 reducer 管理 state 的各个部分。
  3. 生成 store。const store = createStore(mainReducer)
  4. 设置监听器。 store.subscribe(listener)
  5. 使用 dispatch 更新状态。store.dispatch(...)