Redux Toolkit 是个好东西
前言
hello 朋友们,我又来分享技术调研了,芜湖~
这次是 Readux Toolkit,它配合我上次调研的 proxy-memoize
一起来优化一下我们项目现在的状态管理。
相信大部分小伙伴还是对 redux
更了解,那这个Readux Toolkit又是个啥东西的,能带来啥,怎么用。那这篇文章可能能帮你解决这几个疑问。当然如果你想更详细的了解的话肯定是要看官网的啦。
那么话不多说,进入正题吧
基于Redux优化
首先毫无疑问,Readux Toolkit是基于Redux的一系列优化,那优化了redux的什么呢,这里我就简要的讲一讲redux可能存在的缺点(从一些角度说它的缺点也是了优点,因场景而异啦):
- 我们很多状态都要抽象到 store,一个变化就要对应编写 action,reducer
- 需要几个软件包来使Redux与React一起工作,例如redux-thunk、reselect
- Redux的一些理念导致我们需要写很多样板代码
- 配置 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 dispatchespending/fulfilled/rejected
的action typescreateEntityAdapter
: 生成一组可重用的还原器和选择器来管理存储中的规范化数据- reselect库中的
createSelector
utility,为了方便使用而re-exported。
configureStore
step1 是 configureStore,这个必不可少,用来创建一个空的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需要:
- 一个字符串名来标识该片
- 一个初始状态值
- 一个或多个 reducer 函数来定义如何更新该状态 创建这个slice能干嘛?可以导出生成的 Redux 动作创建器和整个片的 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 函数生成动作创建器
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')
);
总结
这里简要的讲一下这个简单例子的整体的步骤:
- 使用 configureStore 创建 Redux 存储
- configureStore 接受作为命名参数的 reducer 函数
- configureStore 自动设置好了默认设置
- 在 组件外包裹 React-Redux < Provider > 组件
- < Provider store = { store } >
- 使用字符串名称、初始 state 和 reducer 函数调用 createSlice
- Reducer 函数可能使用 Immer“变异(mutate)”状态
- 导出生成的slice reducer 和 action creators
- 使用 useSelector 钩子从 store 中读取数据
- 使用 useDispatch 钩子获取 dispatch 函数,并根据需要进行 dispatch actions 操作
OK,大概就总结道这里了,你会发现还有一些主要的api没有讲到,比如很重要的createReducer 和 createAction这些还没讲,但是这个小应用也能实现了(这个例子的场景限制发挥了呀)。
那其实你知道这些基本就能使用了,还有就是这篇也没讲到 use Redux Toolkit and React-Redux with TypeScript,下篇我们详细讲一下搭配 TypeScript 如何使用以及他的好处吧。
🌸🌸🌸🌸🌸
非常感谢你看到这,如果觉得不错的话点个赞 ⭐ 吧
今天也是在努力变强不变秃的 HearLing
呀 💪
🌸🌸🌸🌸🌸