redux / react-redux 核心属性概述

| 3.1k字 | 12分钟

梳理此文的目的

旨在深层次解读 redux 属性的概念、用法、注意事项等,帮助其对 redux 理解的更充分

Redux是什么?

Redux是JavaScript状态容器,能提供可预测化的状态管理。

为什么要用Redux?

前端复杂性的根本原因是大量无规律的交互和异步操作。
变化和异步操作的相同作用都是改变了当前View的状态,但是它们的无规律性导致了前端的复杂,而且随着代码量越来越大,我们要维护的状态也越来越多。
我们很容易就对这些状态何时发生、为什么发生以及怎么发生的失去控制。那么怎样才能让这些状态变化能被我们预先掌握,可以复制追踪呢?
这就是Redux设计的动机所在,Redux试图让每个State变化都是可预测的,将应用中所有的动作与状态都统一管理,让一切有据可循。

react-20211130223726.png
如上图所示,如果我们的页面比较复杂,又没有用任何数据层框架的话,就是图片上这个样子:交互上存在父子、子父、兄弟组件间通信,数据也存在跨层、反向的数据流。

这样的话,我们维护起来就会特别困难,那么我们理想的应用状态是什么样呢?看下图:

react-20211130223822.png

架构层面上讲,我们希望UI跟数据和逻辑分离,UI只负责渲染,业务和逻辑交由其它部分处理,从数据流向方面来说, 单向数据流确保了整个流程清晰。

我们之前的操作可以复制、追踪出来,这也是Redux的主要设计思想。

综上,Redux可以做到:

  1. 每个State变化可预测。
  2. 动作与状态统一管理。

Redux 的三大原则

react-20211129222151.png
认识并了解redux, 熟记这张图的执行流程就可以了。

Redux 的三大原则

  1. 单一数据源(Single source of truth) 整个应用的 state 被统一管理在唯一对象数store中
  2. State 是只读的(State is read-only) state 的变化只能通过出发 action 去改变
  3. 使用纯函数来执行修改(Change are made with pure functions)使用纯函数来描述 action,这里管这种函数叫做 reducer

redux

redux 设计理念,web 应用其实是一个状态机,视图与状态是一一对应的,所有状态都保存在一个对象里面。

Store

Store 就是保存数据的地方,可以看成是一个容器,整个应用只能有一个 Store,Store 是整个 redux 的统一操作的入口。

State

  1. Store 对象包含所有数据,如果想得到某个节点的数据,就要对 Store 生成快照,这种时点的数据集合,就叫做 State
  2. 当前时刻的 State,可以通过 store.getState()拿到
  3. Redux 规定,一个 State 对应一个 View,只要 State 相同,View 就相同,你如果知道了 State,那么你就知道了 View 是什么,反之亦然。
import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();

Action

  1. State 的变化会导致 View 层的变化,但是用户接触不到 State,只能接触到 View,所以,State 的变化必须是 View 导致的,Action 就是 View 发出的通知,表示 State 应该要发生变化了。
  2. Action 是一个对象,type 属性是必须要的,表示 Action 的名称,其他属性自由设置。
const action = {
  type: "ADD_TODO", // Action的名称是'ADD_TODO'
  payload: "Learn Redux", // 携带的信息是字符串 'Learn Redux'
};

Action 描述当前发生的事情,改变 State 的唯一办法,就是使用 Action,它会运送数据给 Store

Action Creator

View 要发送多少种信息,就会有多少种 Action,所以就需要定义一个函数来生成 Action,这个函数就叫Action Creator

const ADD_TODO = "TODO";

// 此处的 addTodo 函数就是一个Action Creator
function addTodo(text) {
  return {
    type: ADD_TODO,
    text,
  };
}
const action = addTodo("Learn Redux");

Reducer

  1. Store 收到 Action 之后,必须给出一个新的 state,这样 view 才会发生变化,这种 state 的计算过程就叫 Reducer
  2. Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State
const reducer = function (state, action) {
  return new_state;
};

Dispatch

  1. store.dispatch() 是 View 发出 Action 的唯一方法
const { createStore } from 'redux';
const store = createStore(reducer);

store.dispatch({
  type: 'ADD_TYPE',
  payload: 'Learn Redux'
})
  1. store.dispatch 接受一个 Action 对象作为参数,将它发送出去,结合Action Creator 改写后:
store.dispatch(addTodo("Learn Redux"));

Subscribe

Store 允许使用store.subscribe()方法设置监听函数,一旦 state 发生变化,就会自动化执行这个函数

import { createStore } from "redux";
const store = createStore(reducer);
store.subscribe(listener);

// store.subscribe方法返回一个函数,调用这个函数就可以接触监听
let unsubscribe = store.subscribe(() => console.log(store.getState));
unsubscribe();

Store 的一些使用方式

// 构建store的两种方式
import { createStore } from 'redux';

// 1.不带默认参数
let store = createStore(reducer)

// 2. 构建带默认state的store
let store = createStore(reducer, initialState)

// 构建带有中间件的sotre、applyMiddleware
import { applyMiddleware, createStore } from "redux"

import createLogger from 'redux-logger'; // 日志中间件
const store = createStore({
  reducer,
  initial_state,
  applyMiddleware(createLogger)
});

// 还可以构建带多个中间件的store
const store = createStore({
  reducer,
  applyMiddleware(thunk, promise, logger)
})

Store 的方法

  1. store.getState() 获取整个状态数据对象
  2. store.dispatch() 分发 Action
  3. store.subscribe() 订阅状态数据的变化
import { createStore } from "redux";
let { subscribe, dispatch, getState } = createStore(reducer);

createStore

redux 提供 createStore 这个函数, 用来生成 Store

import { createStore } from "redux";
const store = createStore(fn);

createStore 函数接受另一个函数作为参数,返回新生成的 Store 对象
注:fn 是一个函数,但一般是 reducer

redux thunk

redux-thunk 中间件改造了 redux 的 dispatch 方法允许我们用store.dispatch(fn),fn 可以是一个函数。而且此函数可以接受两个参数:dispatch、getState做为参数。

import { createStore } from 'redux';
const store = {
  reducer,
  applyMiddleware(thunk)
}

applyMiddleWare

Redux 中间件将会在 action 被分发之后、到达 reducer 之前执行,对应到工作流中,它的执行时机
action ⇒ middleWare ⇒ dispatch ⇒ reducer ⇒ nextState

// 若有多个中间件,那么 Redux 会结合它们被“安装”的先后顺序,依序调用这些中间件,这个过程如下所示:
action
  ⇒  middleWare1
    ⇒  middleWare2
      ⇒  middleWare3
        ⇒  dispatch
          ⇒ reducer
            ⇒ nextState

中间件是对 dispatch 的扩展,或者说重写,增强 dispatch 的功能!
applyMiddleWare 是 redux 的原生方法, applyMiddlewares 作用是将所有中间件组成一个函数,源码解读如下(具体看注释的地方):

import compose from "./compose";
/**
 * 传入的参数是原始的dispatch方法,返回的结果是改造后的dispatch方法
 * 通过compose,可以让多个改造函数抽象成一个改造函数
 */
export default function applyMiddleware(...middlewares) {
  // 返回一个函数A,函数A的参数是一个createStore函数
  // 函数A的返回值是函数B,其实也就是一个加强后的createStore函数,大括号内的是函数B的函数体
  return (createStore) => (...args) => {
    // 用参数传递进来的的createStore创建一个store
    // 用来获取dispatch和getState
    const store = createStore(...args);

    // 改造的只是store中的dispatch方法
    let dispatch = () => {
      // 此dispatch的作用是改造完成前调用dispatch打印错误信息
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      );
    };

    // 将每个中间件与我们的state关联起来(通过传入getState方法)得到改造后的函数
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    };

    // middlewares是一个中间件函数数组,中间件函数的返回值是一个改造dispatch的函数
    // 调用数组中的每个中间函数,得到所有的改造函数
    const chain = middlewares.map((middleware) => middleware(middlewareAPI));

    // 将这些改造后的函数通过compose库整合成一个函数
    // 用compose后的函数去改造store的dispatch
    dispatch = compose(...chain)(store.dispatch);
    /**
     * compose方法作用
     * compose(func1,func2,func3)
     * 返回一个函数(...args) =>  func1(fun2(fun3(...args)))
     * 即传入的dispatch函数被func3改造后得到一个新的dispatch函数然后继续被fun2改造....
     * 其实就是函数柯里化的连续调用
     */

    //  返回store, 用改造后的dispatch方法替换store中的dispatch
    return {
      ...store,
      dispatch,
    };
  };
}

Redux 中间件的工作模式,你需要牢牢把握以下两点:

  1. 中间件执行的时机,即 action 被分发之后,reducer 被触发之前
  2. 中间件执行的前提,即 applyMiddleWare 将会对 dispatch 进行改写,使得 dispatch 在触发 reducer 之前,会首先执行对 Redux 中间件的链式调用

react-redux

相比 redux, react-redux 需要掌握额外的 api,遵守它的组件拆分规范

react-redux 将组件拆分成两大类:

  1. UI 组件(presentational component)
    a. 只负责UI呈现(纯组件),不负责业务逻辑
    b. 无状态,所有数据都由 this.props 提供
    c. 不适用redux的api
  2. 容器组件(container component)
    a. 只负责管理数据/逻辑,不负责UI呈现
    b. 带有内部状态
    c. UI组件负责UI的呈现,容器组件负责管理数据和逻辑

react-redux 的规定

  1. 所有的 UI 组件都应该由用户提供
  2. 容器组件则是由react-redux自动生成,也就是说,用户负责视觉层,状态管理全部交给它

connect()连接容器组件和 UI 组件

  1. 说回 connect(),其实它就是地地道道的高级组件(HOC)高级组件的用法,可自行补习功课 higher-order-components
// 源码中,connect 结构,传了四个入参
function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?) {
    ....
}
// 使用示例如下:
// TodoList是 UI 组件,VisibleTodoList就是由 React-Redux 通过connect方法自动生成的容器组件
import { connect } from "react-redux";
const VisibleTodoList = connect()(TodoList);

如果需要定义业务逻辑, 毫无疑问的需要输入/输出逻辑

  1. 输入逻辑, 外部的数据(即 state 对象)如何转换为 UI 组件参数
  2. 输出逻辑, 用户发出的动作如何变为 Action 对象, 从 UI 组件传出去
const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);
  1. mapStateToProps 输入逻辑, mapStateToProps 是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state 对象到(UI 组件的)props 对象的映射关系。
  2. 作为函数, mapStateToProps 执行后应该返回一个对象, 里面的每一个键值对就是一个映射。
const mapStateToProps = (state) => {
  return {
    // getVisibleTodos 只是一个自定义的函数而已,重点关注里面值的传递及获取方式
    todos: getVisibleTodos(state.todos, state.visibilityFilter),
  };
};

mapStateToProps 会订阅 Store,每当 state 更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。

  1. 第一个参数总是 state 对象,它是 redux 中存储的顶层数据。还可以使用第二参数, 代表容器组件的 props 对象,对象内的字段就是该组件需要从 store 中获取的值。
    const mapStateToProps = (state, ownProps) => {
     return {
       active: oweProps.filter === state.visibilityFilter,
     };
    };
    
  2. 如果容器组件参数发生变化, 也会引发 UI 组件重新渲染
  3. connect 方法可以省略 mapStateToProps 参数,那样的话,UI 组件就不会订阅 Store,就是说 Store 的更新不会引起 UI 组件的更新。

mapStateToProps 输出逻辑, 将用户对 UI 组件的操作映射成 Action
用来建立 UI 组件的参数到 store.dispatch 方法的映射, 也就是说, 它定义了哪些用户的操作应该当作 Action, 传给 store,可以是一个函数也可以是一个对象。

mapDispatchToProps 用于建立组件和 store.dispatch 的映射关系。它可以是一个对象,也可以是一个函数,当它是一个函数的时候,第一个参数就是 dispatch,第二个参数是组件自身的 props。

// mapDispatchToProps的对象形式如下:
const mapDispatchToProps = {
  AddAction() {
    return (dispatch) =>
      dispatch({
        type: ADD,
      });
  },
};

Provider

react-redux提供Provider组件,可以让容器组件拿到state

import { createStore } from 'redux';
import { Provider } from 'react-redux'

const store = createStore(reducer)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
)

上例,Provider在根组件外面包了一层,这样App的所有子组件都可以拿到state的值了。

到这里,redux / react-redux 涉及到的一些核心属性基本解读完毕,希望通过本篇文章的解读,对你了解redux又有新的认识。


文章参考:

  1. Redux从设计到源码