欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

如何使用和实施用于反应状态管理的 redux 工具包。

最编程 2024-04-11 20:22:27
...

上一篇文章react-redux 使用与实现原理介绍了在react中使用react-reduxredux中的store注入到组件中从而自动地根据store变化render组件, 但是仍然没有解决注册action typeaction creator比较麻烦,reducer 函数庞大复杂等问题。

本篇文章将介绍redux官方推荐的redux toolkit工具集,使用redux toolkit可以配置 store、定义 reducer,更方便的构建不可变数据state、方便地创建整个状态的切片 slice,无需手动编写任何 action creator 或者 action type

什么是 redux-toolkit

redux-toolkit 是官方的, 开箱即用的且强大的用于高效开发应用的redux工具集,它有如下几个特点:

  • 使用configureStore创建 redux store 简单高效, 减少很多模板代码
  • 使用createReducer 将 action type 映射到 reducer 函数,而不是编写 switch...case 语句
  • 可以添加任意中间件,内置 redux-thunk 中间件, 默认开启 redux-dev-tools
  • 集成 immer, 在 reducer 中创建不可变数据将变得非常简单
  • 使用 createSelector 创建可记忆的 selector 函数, 优化性能

使用

以下面的例子为例我们将实现一个简单的计数器以及一个点击add todo按钮在列表中增加一项的功能,效果如下:

react-redux.gif

store.ts:

import { configureStore, createSlice } from '@reduxjs/toolkit';

interface CounterState {
  value: number
}

const counterInitState: CounterState = {
  value: 0,
}
const counterSlice = createSlice({
  name: 'counter',
  initialState: counterInitState,
  reducers: {
    increment: (state) => {
      state.value += 1
    },
    decrement: (state) => {
      state.value -= 1
    }
  }
});

interface TodoState {
  todos: string[]
}

const todoInitState: TodoState = {
  todos: []
}

const todoSlice = createSlice({
  name: 'todo',
  initialState: todoInitState,
  reducers: {
    addTodo: (state) => {
      state.todos = ['Use Redux']
    }
  }
})

export const counterActions = counterSlice.actions
export const todoActions = todoSlice.actions

export const store = configureStore({
  reducer: {
    counter: counterSlice.reducer,
    todo: todoSlice.reducer,
  },
})

App.tsx:

import React from 'react';
import logo from './logo.svg';
import styles from './App.module.css';
import { useDispatch, useSelector } from 'react-redux';
import { todoActions, counterActions } from './store';

function App() {
  const dispatch = useDispatch();

  const state: any = useSelector((state: any) => state)

  const handleDecrement = () => {
    dispatch(counterActions.decrement())
  }

  const handleIncrement = () => {
    dispatch(counterActions.increment())
  }

  const handleAddTodo = () => {
    dispatch(todoActions.addTodo())
  }

  return (
    <div className={styles.App}>
      <header className={styles['App-header']}>
        <img src={logo} className={styles['App-logo']} alt="logo" />
        <div>
          <div className={styles.row}>
            <button
              className={styles.button}
              aria-label="Decrement value"
              onClick={handleDecrement}
            >
              -
            </button>
            <span className={styles.value}>{state.counter.value}</span>
            <button
              className={styles.button}
              aria-label="Increment value"
              onClick={handleIncrement}
            >
              +
            </button>
          </div>

          <div className={styles.row}>
            <ul>
              todos:
              {
                state.todo.todos.map((item: any) => (<li key={item}>{item}</li>))
              }
            </ul>

            <button
              className={styles.button}
              aria-label="Add todo"
              onClick={handleAddTodo}
            >
              add todo
            </button>
          </div>
        </div>
      </header>
    </div>
  );
}

export default App;

可以看到在上面的代码中通过 createSlice 函数创建了两个状态切片 counterSlicetodoSlice, 并通过 configureStore 函数创建了 store。这样便可以通过 counterSlicetodoSlice 获取对应的 action,并且在reducer中可以直接操作state,内置的 immer 库,会帮我们构建一个不可变数据state并返回。

实现

configureStore

首先来看 configureStore, 实际上是在 configureStore 中调用了 reduxcombineReducers 来组合各个 reducer 然后再返回 store

import { combineReducers, createStore } from 'redux';

function isPlainObject(value: any) {
  if (typeof value !== "object" || value === null) return false;
  return Object.getPrototypeOf(value) === Object.prototype;
}

function configureStore(options: any) {
  let { reducer } = options;
  let rootReducer;
  if (typeof reducer === "function") {
    rootReducer = reducer;
  } else if (isPlainObject(reducer)) {
    rootReducer = combineReducers(reducer);
  }
  return createStore(rootReducer);
}

export default configureStore;

createSlice

接下来实现 redux toolkit 中十分重要的 createSlice 函数的实现,createSlice中用到了 createAction 函数,createAction 函数用于创建 action creatordispatch 函数派发,先来实现 createAction

createAction 接收一个 type, 返回一个 actionCreator, 该 actionCreator 返回一个 action, action.type 即为传入的 type 参数

function createAction(type: string) {
  function actionCreator(...args: any) {
    return {
      type: type,
      payload: args[0]
    };
  }
  actionCreator.toString = function () {
    return "" + type;
  }
  actionCreator.type = type;
  return actionCreator;
}

export default createAction;

此外 createSlice 函数也用到了 createReducer 函数, 它接收一个初始 state, 和一个 reducers 对象, reducers 对象的 key 即为 action.type, value 即为真正的 reducer; createReducer 使得用户可以用对象方式来配置 reducer, 而不是用一个内部使用 switch...case 的函数来定义 reducer

function createReducer(initialState: any, reducers = {} as any) {
  return function (state = initialState, action: { type: string }) {
    let reducer = reducers[action.type];
    if (reducer) return reducer(state, action);
    return state;
  }
}
export default createReducer;

createSlice 实际上就是集成了 createReducercreateAction, 返回一个 reducer 函数和 actions 对象

import { createReducer, createAction } from './'

function createSlice(options: {
  name: string;
  initialState: any;
  reducers: Record<string, Function>;
}) {
  let { name, initialState = {}, reducers = {} } = options;
  let actions: any = {};
  const prefixReducers: any = {};

  Object.keys(reducers).forEach((key) => {
    const type = getType(name, key);
    actions[key] = createAction(type);
    prefixReducers[type] = reducers[key];
  })

  let reducer = createReducer(initialState, prefixReducers);

  return {
    name,
    reducer,
    actions
  };
}

function getType(slice: string, actionKey: string) {
  return slice + "/" + actionKey;
}

export default createSlice;

使用 redux toolkit 能极大地方便用户使用,做到开箱即用,内置的很多诸如 immer, reselect, redux-thunk 等库能帮助用户快速开发应用,可以说是现在使用 redux 的最佳实践了。

曾经 dva 作为 redux 的工具集也流行过一段时间,但是 redux toolkit 在使用上比 dva 方便很多, 而且 redux toolkit 内部也内置了本文没有介绍的 RTK Query, RTK Query是一个强大的数据获取和缓存工具。不过 dva 内置的副作用处理中间件是 redux sagaredux toolkit内置的是 redux thunk, 下一篇文章redux middleware 的使用与实现原理我将介绍 redux 的中间件的使用及其实现原理。