Redux 是什么

redux 和 react-redux

你了解 React Redux 吗?

  • redux
    redux 是 JavaScript 应用的状态容器,是一个 JS 库,提供可预测的状态管理。和 Vuex 是一样的。
    社区通常认为 Redux 是 Flux 的简化设计版本,但它吸收了 Elm 的架构思想,更像一个混合产物。
  • react-redux
    React Redux 是 Redux 的官方 React UI 绑定库。使得 React 组件能够从 Redux store 中读取到数据,并且可以用 dispatchactions 去更新 store 中的 state。

你了解 React Redux 吗?
既然已经有了 Redux,为什么还要使用 React Redux 呢?
将 Redux 和任意的 UI 框架一起使用都需要相同一致的以下几个步骤:

  1. 创建一个 Redux Store;
  2. 订阅更新;
  3. 订阅回调内部:
    i. 获取当前的 store state;
    ii. 提取这部分 UI 需要的数据;
    iii. 使用数据更新 UI。
  4. 如有必要,用初始的 state 去渲染 UI;
  5. 通过 dispatching Redux actions 去响应 UI 层的交互。

虽然可以手动编写上述逻辑,但这样做会变得非常重复。此外,优化 UI 性能需要很复杂的逻辑。

订阅 store,检查更新数据或触发重新渲染的过程可以变得更加通用和可重用。React Redux 作为 React 官方的 React UI 绑定,能帮助提升性能优化,并且 React Redux 拥有庞大的用户社区。

Flux

Flux 是一种使用单向数据流形式来组合 React 组件的应用架构。

你了解 React Redux 吗?

  • View:视图层,即代码中的 React 组件,负责显示用户界面。
  • Store:数据层,维护数据和数据处理的逻辑。
  • Dispatcher:接收 Actions、执行回调函数。是管理数据流动中的中央枢纽,每一个 Store 提供一个回调。
  • Action:驱动 Dispatcher 的 JS 对象,通常用 type 标记。

Flux 我目前还没有接触过,有兴趣的伙伴们可以去看一下阮老师的Flux 架构入门教程

Elm

Elm,一种语言,主要用于网页开发。

你了解 React Redux 吗?

  • 全局单一数据源,不像 Flux 一样有多个 Store;
  • 纯函数,可以保证输入输出的恒定;
  • 静态类型,可以确保运行安全。

Redux 三原则

  • 单一数据源(Single Source of Truth)

    整个应用的 state 被存储在一棵 object 树中,并且这棵 object 树只存在于唯一一个 Store 中。

  • 纯函数 Reducer(Change are made with pure functions)
    为了描述 Action 是如何改变状态树而编写的一个纯函数 Reducer
    Reducer 的函数签名是这样的 (state, action) => newState

  • State 只读(State is read-only)
    唯一可以改变 state 的方法就是触发 Action,Action 是一个用于描述已发生事件的普通对象。
    Redux 的 Store 状态设计的一个主要原则:避免冗余的数据。

Redux 原理

redux 要求我们把数据都放在 Store 公共存储空间。

一个组件改变了 Store 里的数据内容,其他组件就能感知到 Store 的变化,再来取数据,从而间接的实现了这些数据传递的功能。

react-redux 的用法

  1. 使用 configureStore 创建 Redux Store,并自动配置 Redux DevTools 扩展,以便在开发时检查 store。
    // store.js
    import { configureStore } from '@reduxjs/toolkit';
    export default configureStore({
      reducer: {},
    });
    

    Redux Toolkit 是开箱即用的一个高效 Redux 开发工具集。
    它包括几个实用程序功能,可以简化最常见场景下的 Redux 开发,包括配置 store、定义 reducer,不可变的更新逻辑、可以立即创建整个状态的“切片 slice”,而无需手动编写任何 action creator 或 action type。它还自带了一些最常用的 Redux 插件,例如用于异步逻辑 Redux Thunk,用于编写选择器 selector 的函数 Reselect 等。

  2. 为 React 提供 Redux Store。
    创建 store 后,在应用程序外层包裹一个 React Redux <Provider> 组件,使其对 React 组件可用。
    将 store 作为参数传递。
    // main .js
    import React from 'react';
    import { Provider } from 'react-redux';
    import ReactDOM from 'react-dom/client';
    import App from './App.jsx';
    import store from './store'; 
    ReactDOM.createRoot(document.getElementById('root')).render(
      <React.StrictMode>
        <Provider store={store}>
          <App />
        </Provider>
      </React.StrictMode>
    );
    
  3. 使用 createSlice 创建一个 Redux slice reducer。
    创建 slice 需要一个字符串名称来标识 slice。一个初始 state 值,以及一个或多个 reducer 函数来定义如何更新 state 。
    创建 slice 后,可以导出生成的 Redux action creators 和整个 slice reducer 函数。
    // counterSlice.js
    import { createSlice } from '@reduxjs/toolkit';
    export const counterSlice = createSlice({
      name: 'counter',
      initialState: {
        num: 0,
        name: '',
        age: 0,
      },
      reducers: {
        increment: (state, props) => {
          // Redux Toolkit 允许在 reducers 中编写 mutating 逻辑。
          // 它实际上并没有 mutate state,因为它使用了 Immer(https://immerjs.github.io/immer/) 库
          // 它检测到草稿 state 的变化并产生一个全新的基于这些更改的不可变 state
          state.num += 1;
          state.name = props.payload.name;
        },
        decrement: (state, props) => {
          state.num -= 1;
          state.age = props.payload.age;
        },
      },
    });
    export const { increment, decrement } = counterSlice.actions;
    export default counterSlice.reducer;
    
  4. 添加 Slice Reducers 到 Store。
    // store.js
    import { configureStore } from '@reduxjs/toolkit';
    import counterReducer from './counterSlice';
    export default configureStore({
      reducer: {
        counter: counterReducer,
      },
    });
    
  5. 在 React 函数组价中使用 React Redux useSelector/useDispatch hooks。
    • useSelector hook 从 store 读取数据。
    • useDispatch hook 获取 dispatch 函数,并根据需要 dispatch actions。
    // ChildB.jsx
    import React from 'react';
    import { useSelector, useDispatch } from 'react-redux';
    import { increment, decrement } from '../utils/counterSlice';
    const ChildB = () => {
      const state = useSelector((state) => state.counter);
      const { name, age, num } = state;
      const dispatch = useDispatch();
      return (
        <>
          ChildB 中 counter:{num}
          <br />
          名字:{name}
          <br />
          年龄:{age}
          <br />
          <button onClick={() => dispatch(increment({ name: 'zhangsan' }))}>
            increment
          </button>
          <button onClick={() => dispatch(decrement({ age: 20 }))}>
            decrement
          </button>
        </>
      );
    };
    export default ChildB;
    
    类组件中可以使用 connect 去读取 Redux Store。
    connect 接收两个参数:
    • mapStateToProps:在每一次 store state 改变时被调用,它接收整个 store state,并返回该组件需要的数据对象。
    • mapDispatchToProps:此参数可以是一个 function,或一个 object。
      • function:在 component 创建时立马被调用,它接收 dispatch 作为一个参数,并返回一个 object,其中包含使用 dispatch 来 dispatch actions 的函数。
      • object(充满 action creators):每个 action creator 都会变成一个 prop 函数,在调用时会自动 dispatches 其 action。
    // ChildA.jsx - demo1
    import React from 'react';
    import { connect } from 'react-redux';
    import { increment, decrement } from '../utils/counterSlice';
    class ChildA extends React.Component {
      render() {
        const { num, name, age, dispatch } = this.props;
        return (
          <>
            ChildA 中 counter:{num} <br />
            名字:{name}
            <br />
            年龄:{age}
            <br />
            <button onClick={() => dispatch(increment({ name: 'lisa' }))}>
              increment
            </button>
            <button onClick={() => dispatch(decrement({ age: 18 }))}>
              decrement
            </button>
          </>
        );
      }
    }
    export default connect((state) => state.counter)(ChildA);
    // ChildA.jsx - demo2
    import React from 'react';
    import { connect } from 'react-redux';
    import { increment, decrement } from '../utils/counterSlice';
    class ChildA extends React.Component {
      render() {
        const { num, name, age, dispatch } = this.props;
        return (
          <>
            ChildA 中 counter:{num} <br />
            名字:{name}
            <br />
            年龄:{age}
            <br />
              decrement
            </button>
            <button onClick={() => dispatch({type: 'counter/increment', payload: {name: 'lisa'}})}>
              increment
            </button>
            <button onClick={() => dispatch({type: 'counter/decrement', payload: {age: 18}})}>
              decrement
            </button>
          </>
        );
      }
    }
    export default connect((state) => state.counter)(ChildA);
    // ChildA.jsx - demo3
    import React from 'react';
    import { connect } from 'react-redux';
    class ChildA extends React.Component {
      render() {
        const { num, name, age, incre, decre } = this.props;
        return (
          <>
            ChildA 中 counter:{num} <br />
            名字:{name}
            <br />
            年龄:{age}
            <br />
            <button onClick={() => incre({ name: 'lisa' })}>increment</button>
            <button onClick={() => decre({ age: 18 })}>decrement</button>
          </>
        );
      }
    }
    export default connect((state) => state.counter, {
      incre: increment,
      decre: decrement,
    })(ChildA);
    

Redux 中间件

中间件就是在源数据和目标数据之间做处理,有利于程序的可扩展性。

在 Redux 中,中间件的作用在于调用 dispatch 触发 reducer 之前做一些其他操作,也就是说它改变的是执行 dispatch 到触发 reducer 的流程。

有的中间件有次序要求,比如 logger 必须放在最后。

  • 如果使用基础 Redux 中,中间件都需要 applyMiddleware 进行注册,作用是将所有的中间件组成一个数组,依次执行,然后作为第二个参数传入 createStore 中。
    import { creatStore, applyMiddleware } from 'redux';
    import reducer from './reducer';
    const store = creatStore(reducer, applyMiddleware(...middlewares));
    
  • 如果使用 Redux Toolkit,configureStore API 接收的对象参数中有一个 middleware 参数可以添加中间件。
    // store.js
    import { configureStore } from '@reduxjs/toolkit';
    export default configureStore({
      reducer: {},
      middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), ...middlewares]
    });
    

createStore

用于创建 Redux Store,它接收三个参数:

  • reducer:一个纯函数,用于根据先前的状态和给定的 Action 来计算新的状态。
  • preloadedState:可选参数,用于初始化状态。通常在服务端渲染或者从本地存储加载状态时使用。
  • enhancer:可选参数,一个函数,用于增强 store 的功能,例如使用中间件。
function createStore(reducer, preloadedState, enhancer) {
  // 初始化 currentState,用于存储状态
  let currentState = preloadedState;
  // 初始化 currentReducer,用于存储当前的 reducer 函数
  let currentReducer = reducer;
  // 初始化 listeners,用于存储订阅状态变化的回调函数
  let listeners = [];
  // getState 函数,用于获取当前的状态
  function getState() {
    return currentState;
  }
  // dispatch 函数,用于派发 action 到 reducer,更新状态,并触发订阅状态变化的回调函数
  function dispatch(action) {
    currentState = currentReducer(currentState, action);
    listeners.forEach(listener => listener());
    return action;
  }
  // subscribe 函数,用于订阅状态的变化,每当状态发生变化时触发注册的回调函数
  function subscribe(listener) {
    listeners.push(listener);
    return function unsubscribe() {
      listeners = listeners.filter(l => l !== listener);
    };
  }
  // 如果有 enhancer 函数,则使用 enhancer 来增强 createStore
  if (typeof enhancer === 'function') {
    return enhancer(createStore)(reducer, preloadedState);
  }
  // 初始化 currentState,使用一个初始的 "INIT" action 来获取初始状态
  dispatch({ type: '@@redux/INIT' });
  // 返回一个具有 getState、dispatch 和 subscribe 方法的对象,即 Redux store
  return {
    getState,
    dispatch,
    subscribe
  };
}

applyMiddleware

applyMiddleware 是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。

function applyMiddleware(...middlewares) {
  // 返回一个enhancer函数,接收createStore作为参数
  return function(createStore) {
    // 返回一个新的createStore函数,接收reducer和preloadedState作为参数
    return function(reducer, preloadedState) {
      // 调用原始的 createStore 函数创建 store
      const store = createStore(reducer, preloadedState);
      let dispatch = store.dispatch;
      let chain = [];
      // 定义 middleware API
      const middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      };
      // 调用每个 middleware,传入 middlewareAPI
      chain = middlewares.map(middleware => middleware(middlewareAPI));
      // 组合所有中间件,得到一个 dispatch 函数
      dispatch = compose(...chain)(store.dispatch);
      // 返回一个增强版的 store,其中 dispatch 已经被包装
      return {
        ...store,
        dispatch
      };
    };
  };
}

compose

compose 函数实现了将多个函数组合在一起的功能,从右到左,依次实现。

function compose(...funcs) {
  // 如果没有传入任何函数,则直接返回一个接收参数并返回参数的函数
  if (funcs.length === 0) {
    return arg => arg;
  }
  // 如果只传入一个函数,则直接返回该函数
  if (funcs.length === 1) {
    return funcs[0];
  }
  // 返回一个组合了所有函数的新函数
  return funcs.reduce((a, b) => (...args) => a(b(...args)));
}

configureStore

configureStore API 内部自动调用了 applyMiddlewarecomposecreateStore 函数。

import { createStore, applyMiddleware, compose } from 'redux';
import thunkMiddleware from 'redux-thunk'; // 引入 Redux Thunk 中间件
import rootReducer from './reducers'; // 引入根 reducer
/**
 * configureStore 函数用于创建 Redux store
 * @param {Object} preloadedState 初始状态对象,可选
 * @returns {Object} Redux store 对象
 */
function configureStore(preloadedState) {
  // 定义中间件数组
  const middlewares = [thunkMiddleware]; // 引入 Redux Thunk 中间件
  // Redux DevTools Extension
  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
  // 创建 store
  const store = createStore(
    rootReducer, // 根 reducer
    preloadedState, // 初始状态
    composeEnhancers(
      applyMiddleware(...middlewares) // 应用中间件
    )
  );
  return store;
}
export default configureStore;

redux-thunk

处理异步 Action。

  • 如果使用的是 Redux Toolkit,configureStore API 已经默认添加了 thunk 中间件。
  • 如果使用的基础 Redux 的 createStore(已经被废弃),则需要安装。
    $ npm install redux-thunk
    $ yarn add redux-thunk
    
    // index.jsx
    import { creatStore, applyMiddleware } from 'redux';
    import { thunk } from 'redux-thunk';
    import reducer from './reducer';
    const store = creatStore(reducer, applyMiddleware(thunk));
    

redux-logger

实现日志功能。

$ npm install redux-logger
$ yarn add redux-logger
  • 如果使用的是 Redux Toolkit:
    // store.js
    import { configureStore } from '@reduxjs/toolkit';
    import logger from 'redux-logger';
    import counterReducer from './counterSlice';
    export default configureStore({
      reducer: {
        counter: counterReducer,
      },
      middleware: (getDefaultMiddleware) => [...getDefaultMiddleware(), logger]
    });
    
  • 如果使用的是基础 Redux
    // index.jsx
    import { creatStore, applyMiddleware } from 'redux';
    import logger from 'redux-logger';
    import reducer from './reducer';
    const store = creatStore(reducer, applyMiddleware(logger));
    

你了解 React Redux 吗?

参考:Redux 官方文档
参考:React Redux 官方文档
参考:Redux 工具包 官方文档

如有问题,欢迎指正~

你了解 React Redux 吗?