Redux Toolkit 是个好东西

小狮子前端

共 6850字,需浏览 14分钟

 · 2021-11-03

前言

hello 朋友们,我又来分享技术调研了,芜湖~

这次是 Readux Toolkit,它配合我上次调研的 proxy-memoize 一起来优化一下我们项目现在的状态管理。

相信大部分小伙伴还是对 redux 更了解,那这个Readux Toolkit又是个啥东西的,能带来啥,怎么用。那这篇文章可能能帮你解决这几个疑问。当然如果你想更详细的了解的话肯定是要看官网的啦。

那么话不多说,进入正题吧

基于Redux优化

首先毫无疑问,Readux Toolkit是基于Redux的一系列优化,那优化了redux的什么呢,这里我就简要的讲一讲redux可能存在的缺点(从一些角度说它的缺点也是了优点,因场景而异啦):

  1. 我们很多状态都要抽象到 store,一个变化就要对应编写 action,reducer
  2. 需要几个软件包来使Redux与React一起工作,例如redux-thunk、reselect
  3. Redux的一些理念导致我们需要写很多样板代码
  4. 配置 Redux store太复杂

当然我们也不全是因为redux他的这些所谓的缺点,而非要卷来优化哈,其实也是因为调研Readux Toolkit我才发现这redux的缺点,虽然是上级给的任务,但是调研之后发现还是真香。

需了解的知识

首先当然是要了解redux知识啦,有redux知识为了方便更迅速理解Readux Toolkit的实现或者他的妙用,还需要先了解他的核心依赖:

  • immer
  • redux
  • redux-thunk
  • reselect

immer

这几个中我是不了解这个immer的,其他基本略知一二,那么就看看这个immer库是个啥吧:

这个库,它允许我们把state的 不变的(immutable) 特性转化为 可变的(mutable)

具体上的实现它是利用了 proxy,当我们对 state 进行修改,proxy对象会拦截,并且按顺序替换上层对象,返回的新对象。看上去就好像自动帮你直接修改了state

api

首先看看整体的Api,然后再详细说说可能会常用的:

  • configureStore (): 包装 createStore 以提供简化的配置选项和良好的默认设置。它可以自动组合你的slice reducers,添加你提供的任何 Redux 中间件,默认包括 Redux-thunk,并启用 Redux DevTools 扩展。
  • createReducer () : 它允许您为 case reducer 函数提供一个动作类型查找表,而不是编写 switch 语句。此外,它还自动使用 immer 库,让您使用普通的可变代码编写更简单的不可变更新,比如 state.todos [3].complete = true。
  • createAction () : 为给定的动作类型字符串生成动作创建器函数。函数本身定义了 toString () ,因此可以使用它来代替类型常量。
  • createSlice () : 接受 reducer 函数的对象、片名和初始状态值,并自动生成带有相应动作创建器和动作类型的 slice reducer。
  • createAsyncThunk: 接受一个操作类型字符串和一个返回promise函数,并生成一个 thunk,该 thunk 根据该promise dispatches pending/fulfilled/rejected的action types
  • createEntityAdapter: 生成一组可重用的还原器和选择器来管理存储中的规范化数据
  • reselect库中的 createSelector utility,为了方便使用而re-exported。

configureStore

step1configureStore,这个必不可少,用来创建一个空的Redux store,同时这里呢会自动配置 Redux DevTools 扩展,以便检查存储:

import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
    reducer: {},
})

step2 是要< provider > 来使 redux 对 React 组件可用,将导出的store当作prop传递给它,这一块不必多说

createSlice

step3 这里会有点不一样了,我们要通过 createSlice 创建一个Redux状态切片(Redux State Slice),创建这个slice需要:

  1. 一个字符串名来标识该片
  2. 一个初始状态值
  3. 一个或多个 reducer 函数来定义如何更新该状态 创建这个slice能干嘛?可以导出生成的 Redux 动作创建器和整个片的 reducer 函数:
import { createSlice } from '@reduxjs/toolkit'

const initialState = {
  value0,
}

export const counterSlice = createSlice({
  name'counter',
  initialState,
  reducers: {
    increment(state) => {
      /** 
       * Redux Toolkit 允许我们在还原器中编写“可变的(mutable)”逻辑。
       * 它实际上并没有改变状态,因为它使用 Immer 库,
       * 它将检测对"draft state" 的更改,并根据这些更改生成
       * 一个全新的不可变状态
       */

      state.value += 1
    },
    decrement(state) => {
      state.value -= 1
    },
    incrementByAmount(state, action) => {
      state.value += action.payload
    },
  },
})

// 为每个 reducer 函数生成动作创建器
export const { increment, decrement, incrementByAmount } = counterSlice.actions

export default counterSlice.reducer

结合这个例子,可以清楚的看到这个createSlice接收的:一个字符串名来标识该片也就是name,一个初始状态值initialState,以及多个reducer行数。并且为每个 reducer 函数生成动作创建器。

它有啥作用或者其他好处呢?可能一小部分人不看代码,我把注释给拿下来。

我们知道 Redux 它是要求我们通过制作数据副本和更新副本来编写所有状态更新的。然而, createSlice 和 createReducer 在内部使用 Immer 来允许我们编写“可变的(mutable)”的更新逻辑,使其成为正确的不可变更的更新。

Redux Toolkit 允许我们在还原器中编写“mutable”逻辑。它实际上并没有改变状态,因为它使用 Immer 库,检测对“draft state”的更改,并根据这些更改生成一个全新的不可变状态

step 4  我们需要从上面的创建的空的 store 导入 reducer 函数并将其添加到我们的存储中,通过在 reducer 参数中定义一个字段,告诉 store 使用这个 slice reducer 函数来处理该状态的所有更新。

import { configureStore } from '@reduxjs/toolkit'
import counterReducer from '../features/counter/counterSlice'

export default configureStore({
  reducer: {
    counter: counterReducer,
  },
})

step 5 现在我们可以使用 React-Redux hook 让 React 组件与 Redux 存储交互。我们可以使用 useSelector 从存储中读取数据,并使用 useDispatch 分派操作。

理解的话我们看这个 counter 组件的例子:

import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import { decrement, increment } from './counterSlice'

export function Counter() {
  const count = useSelector((state) => state.counter.value)
  const dispatch = useDispatch()

  return (
    <div>
      <div>
        <button onClick={() => dispatch(increment())} >
          增加+
        button>

        <span>{count}span>
        <button onClick={() => dispatch(decrement())} >
          减少-
        button>
      div>
    div>
  )
}

当点击+、-按钮时的动作,分析:

  • 相应的 Redux action 将被派发(dispatched)到存储区(store)
  • 这个 counter slice reducer将观测actions并更新其状态
  • < Counter > 组件将观测到存储(store)中新的状态值,并使用新数据re-render自己

例子

这里也放一个简单的例子,可以访问codesandbox的可以戳这里,也可以去官网找这个例子。

store.js 文件

import { configureStore } from '@reduxjs/toolkit';
import counterReducer from '../features/counter/counterSlice';

export default configureStore({
    reducer: {
        counter: counterReducer,
    },
});

counterSlice.js 文件

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

export const slice = createSlice({
    name'counter',
    initialState: {
        value0,
    },
    reducers: {
        incrementstate => {
            state.value += 1;
        },
        decrementstate => {
            state.value -= 1;
        },
        incrementByAmount(state, action) => {
            state.value += action.payload;
        },
    },
});

export const { increment, decrement, incrementByAmount } = slice.actions;

export const incrementAsync = amount => dispatch => {
    setTimeout(() => {
        dispatch(incrementByAmount(amount));
    }, 1000);
};

export const selectCount = state => state.counter.value;
export default slice.reducer;

Counter.js 文件

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import {
    decrement,
    increment,
    incrementByAmount,
    incrementAsync,
    selectCount,
from './counterSlice';
import styles from './Counter.module.css';

export function Counter() {
const count = useSelector(selectCount);
const dispatch = useDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');

return (
<div>
    <div>
        <button onClick={() => dispatch(increment())} >
        +
        button>

        <span>{count}span>
        <button onClick={() => dispatch(decrement())} >
        -
        button>
    div>

    <div>
        <input
        value={incrementAmount}
        onChange={e =>
 setIncrementAmount(e.target.value)}
        />

        <button
        onClick={() =>

        dispatch(incrementByAmount(Number(incrementAmount) || 0))
        }
        >
        Add Amount
        button>

        <button onClick={() => dispatch(incrementAsync(Number(incrementAmount) || 0))} >
        Add Async
        button>
    div>
div>
);
}

index.js 文件

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './index.css';
import App from './App';
import store from './app/store';

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

总结

这里简要的讲一下这个简单例子的整体的步骤:

  1. 使用 configureStore 创建 Redux 存储
  • configureStore 接受作为命名参数的 reducer 函数
  • configureStore 自动设置好了默认设置
向 React 应用程序组件提供 Redux 存储
  • 在 组件外包裹 React-Redux < Provider > 组件
  • < Provider store = { store } >
使用 createSlice 创建一个 Redux“ slice”reducer
  • 使用字符串名称、初始 state 和 reducer 函数调用 createSlice
  • Reducer 函数可能使用 Immer“变异(mutate)”状态
  • 导出生成的slice reducer 和 action creators
在 React 组件中使用 redux useSelector/useDispatch 挂钩
  • 使用 useSelector 钩子从 store 中读取数据
  • 使用 useDispatch 钩子获取 dispatch 函数,并根据需要进行 dispatch actions 操作

OK,大概就总结道这里了,你会发现还有一些主要的api没有讲到,比如很重要的createReducer 和 createAction这些还没讲,但是这个小应用也能实现了(这个例子的场景限制发挥了呀)。

那其实你知道这些基本就能使用了,还有就是这篇也没讲到 use Redux Toolkit and React-Redux with TypeScript,下篇我们详细讲一下搭配 TypeScript 如何使用以及他的好处吧。

🌸🌸🌸🌸🌸

非常感谢你看到这,如果觉得不错的话点个赞 ⭐ 吧

今天也是在努力变强不变秃的 HearLing 呀 💪

🌸🌸🌸🌸🌸


浏览 12
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报