likes
comments
collection
share

React 状态管理:从现在开始拥抱redux-toolkit

作者站长头像
站长
· 阅读数 14

在之前的文章:从零开始学习React-5:状态与状态管理,我们简单的介绍目前最流行的状态管理库 redux。今天我们介绍由 redux 团队推出的,可以更高效、更方便使用 redux 的另一个工具:Redux-Toolkit(简称:RTK)

为什么是 redux-toolkit

redux 被设计的非常灵活,这也带来了一些问题,例如如果我们直接使用 redux 会有如下几点不便之处:

  1. 模板代码太多

    在上一篇文章示例中可能感觉还不明显,其实正常项目中,每一个 Action 一般都会有这样几个模板代码:

    • 声明一个静态的 type 字符串,例如 const ADD_TODO = 'addTodo';
    • 声明一个 action creator 函数,例如 export const addTodo = (data) => ({ type: ADD_TODO, data });
    • reducer 函数内需要根据 action 的 type 写 switch - case

    action 对象的 type 一般都是唯一的一个字符串,因为 dispatch 一个 action 时,所有的 reducer 函数都会收到。

    这带来了一个隐藏的问题,如果我们在多个 reducer 函数中 case 了 一个相同的 type 字符串,这两个 reducer 都会执行动作,改变状态。

    这也就是为什么 Redux 推荐我们使用 action creator 函数,这可以有效的避免写错导致bug。

  2. 仅安装 redux 可能无法胜任。我们一般还需要安装 比如 redux-thunk(异步)、immer(状态不可变) 等库来配合 redux 工作。

    这就导致了 redux 的配置比较复杂,很难做到开箱即用,即使有的配置已经成为了某种意义上的 “ 最佳实践 ”。

RTK 通过几个函数很好的帮我们解决了这些细节问题,几乎做到了开箱即用。

如何使用 RTK

这里我们还是使用之前文章的 TodoList 来作为例子,我们将它修改成使用 RTK 实现。

  1. 安装 RTK : yarn add @reduxjs/toolkit react-redux

    查看其依赖我们可以看到 RTK 已经依赖了 redux 与 redux-thunk了

      "dependencies": {
        "immer": "^9.0.7",
        "redux": "^4.1.2",
        "redux-thunk": "^2.4.1",
        "reselect": "^4.1.5"
      },
    
  2. 创建 todo.slice.js Slice 切片

import { createSlice } from "@reduxjs/toolkit";

const initialState = [];

// 固定格式,创建 slice 对象
const todoSlice = createSlice({
    name: 'todo',
    initialState,
    // 相当于原来reducer中的case
    reducers: {
        // 后面文中我们将这类函数称之为 case
        addTodo: (state, action) => {
            state.push(action.payload);
        },
        delTodo: (state, action) => {
            // 这里要注意不能使用之前的filter方式,因为immer的原因,我们必须直接操作state
            const index = state.findIndex(item => item.id === action.payload);
            state.splice(index, 1);
        }
    }
});

// 官方推荐使用 ES6 解构和导出语法
// 提取action creator 对象与reducer函数
const { reducer, actions } = todoSlice;
// 提取并导出根据reducers命名的 action creator 函数
export const { addTodo, delTodo } = actions;
// 导出 reducer 函数
export default reducer;

这里我们按照官方推荐使用 ES6 解构和导出语法,来获取 action creator 与 reducer。

从代码可以看出,通过 createSlice 函数,我们减少了绝大多数的模板代码,我们无需再写 type、action creator,这一切 RTK 都帮我们在背后默默实现了。

而且我们无需再关注状态不可变,我们只需像操作普通对象一样,在 reducers 中的 case 函数中随意操作对象,这一切都被 immer 帮我们搞定了。

你可能会好奇,最终通过 action creator 函数返回的是一个这样的 action 对象:

{type: 'todo/addTodo', payload: ...}

我们在 reducers 中写的 case 中拿到的 action 就是这样的固定格式,这一定需要注意,因为在我们过去使用 redux 时,并没有规范 action 对象的类型。

ps:可以看到 RTK 自动的帮我们将 type 命名为了 name + case函数名 这样的格式,这对于我们调试也是非常方便的,起名困难症表示 RTK 赛高!

**注意:**其实代码修改到这里,我们的程序就可以正常运行了,这也是 RTK 的一大优势,我们可以不必全部重新改造我们的程序,它没有增加我们的心智负担,并不需要我们重新去掌握一个库,而是通过这些工具性质的函数,帮助我们更好的使用 Redux。

  1. 使用 configureStore 函数创建 store 对象
import { configureStore } from "@reduxjs/toolkit"
import todoReducer from "./todo.slice";

export default configureStore({
    reducer: {
        todos: todoReducer
        // ... 其他的 reducer
    }
});

configureStore 函数接收一个配置redux的对象作为参数,具有如下选项:

  • reducer 必选参数,可以是一个 reducer 函数也可以是一个由 slice reducer 构成的对象

    如果是 reducer 函数,就直接将他作为根 reducer,如果是一个对象,就自动帮我们调用 combineReducers 函数来合并多个 reducer 创建根 reducer 函数

  • middleware 中间件

  • devTools 是否启用 Redux DevTools

更多配置,请查看文档:redux-toolkit.js.org/api/configu…

剩余的使用是与原来一模一样的,我们无需改动其他代码!

其他 api 介绍

除了我们上面介绍的这种比较完整的改造,其实 RTK 还提供了很多粒度更小的 api,来方便我们改造原有的模板代码,这里我们简单介绍几个:

1. createAction

这个函数可以帮我们减少创建 action 时的模板代码,还有我们的例子来说明:

// before
const ADD_TODO = 'ADD_TODO';
const DEL_TODO = 'DEL_TODO';

export const addTodo = (payload) => ({ type: ADD_TODO, payload });
export const delTodo = (payload) => ({ type: DEL_TODO, payload });

const action = addTodo({...});
                        
// after
const addTodo = createAction('ADD_TODO')
const delTodo = createAction('DEL_TODO')

const action = addTodo({...});

我们完全不需要再写 action creator 函数了,直接使用 createAction 就可以帮我们创建,它的源码其实也很简单:

export function createAction(type: string, prepareAction?: Function): any {
   // action creator 函数
  function actionCreator(...args: any[]) {
    if (prepareAction) {
      let prepared = prepareAction(...args)
      if (!prepared) {
        throw new Error('prepareAction did not return an object')
      }

      return {
        type,
        payload: prepared.payload,
        ...('meta' in prepared && { meta: prepared.meta }),
        ...('error' in prepared && { error: prepared.error }),
      }
    }
    //action creator 函数返回的固定样式的 action 对象
    return { type, payload: args[0] }
  }

  // 自定义 action creator 函数的 toString 
  actionCreator.toString = () => `${type}`

  actionCreator.type = type

  actionCreator.match = (action: Action<unknown>): action is PayloadAction =>
    action.type === type

  return actionCreator
}

2.createReducer()

这个函数可以帮我们简化 reducer 函数的创建,并在其内部使用了 immer ,让我们可以直接操作 state,而不用再考虑不可变的问题,极大的简化了更新状态的逻辑。我们一般用 createAction 配合使用

// before
const initState = [];
//模板代码 action type 常量
const ADD_TODO = 'ADD_TODO';
const DEL_TODO = 'DEL_TODO';
//模板代码用于创建 action 的 action creator 函数
export const addTodo = (payload) => ({ type: ADD_TODO, payload });
export const delTodo = (payload) => ({ type: DEL_TODO, payload });
// reducer 函数
export default function todoReducer(preState = initState, action) {
    const { type, payload } = action;
    switch (type) {
        case ADD_TODO:
            return [...preState, payload];
        case DEL_TODO:
            return preState.filter(item => item.id !== payload);
        default:
            return preState;
    }
}

//after
import { createReducer, createAction } from "@reduxjs/toolkit";
const initialState = [];
//使用 createAction 函数创建 action creator
export const addTodo = createAction('ADD_TODO')
export const delTodo = createAction('DEL_TODO')
//使用createReducer生成reducer函数
export default createReducer(initialState, {
    [addTodo]: (state, action) => {
        state.push(action.payload)
    },
    [delTodo]: (state, action) => {
        const index = state.findIndex(item => item.id === action.payload);
        state.splice(index, 1);
    }
})

现在我们的代码变得更加简洁,几乎没有了模板代码,非常的优雅!

除了上面我演示的这种方式以外,RTK 还支持使用 Builder Callback 这种方式,大家要是感兴趣可以自行查看文档:redux-toolkit.js.org/api/createR…

好了,关于 RTK 的话题我们就介绍到这里了,更多有趣的内容请关注我的专栏!