前言

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 动作创建器(action creators)和整个片的 reducer 函数:
import { createSlice } from '@reduxjs/toolkit'

const initialState = {
value: 0,
}

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 函数生成动作创建器(Action creators)
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: {
value: 0,
},
reducers: {
increment: state => {
state.value += 1;
},
decrement: state => {
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 自动设置好了默认设置
  2. 向 React 应用程序组件提供 Redux 存储
    • 在 组件外包裹 React-Redux < Provider > 组件
    • < Provider store = { store } >
  3. 使用 createSlice 创建一个 Redux“ slice”reducer
    • 使用字符串名称、初始 state 和 reducer 函数调用 createSlice
    • Reducer 函数可能使用 Immer“mutate”状态
    • 导出生成的slice reducer 和 action creators
  4. 在 React 组件中使用 redux useSelector/useDispatch 挂钩
    • 使用 useSelector 钩子从 store 中读取数据
    • 使用 useDispatch 钩子获取 dispatch 函数,并根据需要进行 dispatch actions 操作

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

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

🌸🌸🌸🌸🌸

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

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

🌸🌸🌸🌸🌸