React?TypeScript?應(yīng)用中便捷使用Redux?Toolkit方法詳解
前言
本文介紹的主要內(nèi)容是 Redux-Toolkit 在 React + TypeScript 大型應(yīng)用中的實(shí)踐,主要解決的問(wèn)題是使用 createSlice 的前提下消費(fèi) redux 狀態(tài)仍舊有點(diǎn)繁瑣的問(wèn)題。
閱讀本文需要的前置知識(shí):了解 React 、Redux-Toolkit 、TypeScript 的使用。
關(guān)于 Redux-Toolkit 提供的各種函數(shù)的使用,大家可以去官網(wǎng) redux-toolkit.js.org/ 學(xué)習(xí)。
背景
前陣子接到一個(gè)任務(wù):在使用 redux 作為狀態(tài)管理工具的前提下,優(yōu)化一下消費(fèi) redux 的步驟,并制定一套使用規(guī)范,讓大家在開(kāi)發(fā)這個(gè)項(xiàng)目消費(fèi) redux 狀態(tài)時(shí)能按照規(guī)范來(lái)。
說(shuō)到簡(jiǎn)化消費(fèi) redux 步驟,我第一時(shí)間想到的就是 redux 官方推薦的 Redux-Toolkit,于是我就去學(xué)習(xí)了一下 Redux-Toolkit。
了解完官網(wǎng)和網(wǎng)上各種文章后,我知道了 Redux-Toolkit 在項(xiàng)目中的使用,但是仍然有一個(gè)疑問(wèn):使用了 createSlice 后,仍然需要在項(xiàng)目的組件中使用 useDispatch 來(lái)更新?tīng)顟B(tài),還是有點(diǎn)麻煩。對(duì)于組件使用者來(lái)說(shuō),有沒(méi)有更方便的方式消費(fèi) redux 狀態(tài)?
在網(wǎng)上逛來(lái)逛去,沒(méi)找到有人發(fā)相關(guān)的文章,所以我準(zhǔn)備自己動(dòng)手試試看。
Redux-Toolkit 常規(guī)使用
我們先來(lái)看看目前使用 Redux-Toolkit 消費(fèi) redux 狀態(tài)的方式。舉個(gè)例子,假設(shè)我們現(xiàn)在的業(yè)務(wù)和蛋糕有關(guān),有兩個(gè)狀態(tài)存在 redux,分別是 nameOfCake 和 numOfCakes,我們使用 createSlice 來(lái)創(chuàng)建 reducer 和 actions:
// 文件位置: app/src/store/reducers/cake.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type InitialState = {
numOfCakes: number;
nameOfCake: string;
};
const initialState: InitialState = {
numOfCakes: 20,
nameOfCake: 'great cake',
};
const cakeSlice = createSlice({
name: 'cake',
initialState,
reducers: {
updateCakeNum: (state, action: PayloadAction<number>) => {
state.numOfCakes = action.payload;
},
updateCakeName: (state, action: PayloadAction<string>) => {
state.nameOfCake = action.payload;
},
},
});
export default cakeSlice.reducer;
export const { updateCakeNum, updateCakeName } = cakeSlice.actions;
接著我們?cè)诮M件中消費(fèi):
// 文件位置: app/src/pages/Cake/components/CakeView/index.tsx
import { RootState } from '@/src/store';
import { useSelector, useDispatch } from 'react-redux';
import {
useRecordReduxFunction,
updateCakeNum,
updateCakeName,
} from '@/src/store/reducers/cake';
export const CakeView = () => {
const numOfCakes = useSelector((state: RootState) => state.cake.numOfCakes);
const nameOfCake = useSelector((state: RootState) => state.cake.nameOfCake);
const dispatch = useDispatch();
const updateNum = () => {
dispatch(updateCakeNum(100));
};
const updateName = () => {
dispatch(updateCakeName('best cake'));
};
return (
<div>
<h3>Number of cakes - {numOfCakes}</h3>
<h3>Name of cakes - {nameOfCake}</h3>
<button onClick={updateName}>change cake's name</button>
<button onClick={updateNum}>change cake's number</button>
</div>
);
};
現(xiàn)狀的繁瑣點(diǎn):
- 每次使用 useSelector 來(lái)獲取 redux 中狀態(tài)的時(shí)候,都需要給 state 加一個(gè) ts 類型 RootState
- 每次修改 redux 狀態(tài)時(shí),都需要引入 useDispatch 和一個(gè) updateData,再將調(diào)用 updateData 返回的action 給 dispatch
我們的目標(biāo)就是優(yōu)化現(xiàn)狀的這兩個(gè)繁瑣點(diǎn)。
優(yōu)化方案
優(yōu)化 useDispatch 和 useSelector
對(duì)于繁瑣點(diǎn)1,我們可以對(duì) useDispatch 和 useSelector 進(jìn)行簡(jiǎn)單的封裝,增加 ts 類型校驗(yàn)。代碼如下:
// 文件位置: app/src/hooks/useReduxHook.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../store';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
其中 RootState 和 AppDispatch 在 store 中導(dǎo)出:
import { configureStore } from '@reduxjs/toolkit';
import cakeReducer from './reducers/cake';
const store = configureStore({
reducer: {
cake: cakeReducer,
}
})
export default store;
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
通過(guò)簡(jiǎn)單的封裝,我們?cè)跇I(yè)務(wù)層就用 useAppSelector 和 useAppDispatch 代替 useSelector 和 useDispatch:
import { useAppSelector, useAppDispatch } from '@src/hooks/useReduxHook';
// ...
export const CakeView = () => {
const numOfCakes = useAppSelector((state) => state.cake.numOfCakes);
const nameOfCake = useAppSelector((state) => state.cake.nameOfCake);
const dispatch = useAppDispatch();
// ...
return (
// ...
);
};
可以看到這樣就有 ts 類型提示或者校驗(yàn)了:

優(yōu)化修改 redux 狀態(tài)的步驟
我們現(xiàn)在想要在業(yè)務(wù)組件中修改 redux 狀態(tài),我們就需要引入 useDispatch 和通過(guò) createSlice 生成的 updateData 函數(shù),再調(diào)用 dispatch(updateData(data)) 來(lái)更新?tīng)顟B(tài)。
能不能優(yōu)化成,業(yè)務(wù)組件只需要調(diào)用一個(gè) updateData 函數(shù),就可以更新 redux 狀態(tài)呢?
最簡(jiǎn)單的方式就是將 dispatch(updateData(data)) 給抽出去:
const useUpdateCakeName = () => {
const dispatch = useAppDispatch();
return (payload: InitialState['nameOfCake']) => {
dispatch(updateCakeName(payload));
};
};
但是這對(duì)于開(kāi)發(fā)者來(lái)講,換湯不換藥,還是需要寫一個(gè) hook 來(lái) dispatch action。
那我們就來(lái)寫一個(gè)能自動(dòng)生成 「用于dispatch action 的 updateData 函數(shù)」的 hook 吧。先來(lái)個(gè) js 版:
const useCakeReduxFunction = (action) => {
const dispatch = useAppDispatch();
return (payload) => {
dispatch(cakeSlice.actions[action](payload));
};
};
這個(gè)倒是好用了一些,業(yè)務(wù)組件使用起來(lái)是這樣的:
import { useCakeReduxFunction } from '@/src/store/reducers/cake';
// ...
export const CakeView = () => {
const updateNum = useCakeReduxFunction('updateCakeNum');
const updateName = useCakeReduxFunction('updateCakeName');
// ...
return (
// ...
);
};
但是還有個(gè)問(wèn)題,這樣的話每個(gè) reducer 里都要寫一個(gè) useDataReduxFunction,還是不夠便捷。我們就自然而然的想到寫一個(gè) useCreateReduxFunction 來(lái)簡(jiǎn)化開(kāi)發(fā)流程。useCreateReduxFunction 的代碼如下;
type GetArrFirst<T> = T extends [infer Res, ...infer P] ? Res : unknown;
export const useCreateReduxFunction = <S extends Slice>(slice: S) => {
return <T extends keyof S['actions']>(name: T) => {
const dispatch = useAppDispatch();
const actionCreator = (slice.actions as S['actions'])[name];
return (payload: GetArrFirst<Parameters<typeof actionCreator>>) => {
dispatch(actionCreator(payload));
};
};
};
這樣就好辦了,我們?cè)?reducer 中這樣使用:
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { useCreateReduxFunction } from '../../app/hooks';
type InitialState = {
numOfCakes: number;
cakeName: string;
};
const initialState: InitialState = {
numOfCakes: 20,
cakeName: 'great cake',
};
const cakeSlice = createSlice({
name: 'cake',
initialState,
reducers: {
updateCakeNum: (state, action: PayloadAction<number>) => {
state.numOfCakes = action.payload;
},
updateCakeName: (state, action: PayloadAction<string>) => {
state.cakeName = action.payload;
},
},
});
export const useCakeReduxFunction = useCreateReduxFunction(cakeSlice);
export default cakeSlice.reducer;
export const { updateCakeNum, updateCakeName } = cakeSlice.actions;
在業(yè)務(wù)方這么使用:
import { useAppSelector } from '@src/hooks/useReduxHook';
import { useCakeReduxFunction } from '@src/store/reducers/cake';
export const CakeView = () => {
const numOfCakes = useAppSelector((state) => state.cake.numOfCakes);
const nameOfCake = useAppSelector((state) => state.cake.cakeName);
const updateNum = useCakeReduxFunction('updateCakeNum');
const updateName = useCakeReduxFunction('updateCakeName');
return (
<div>
<h3>Number of cakes - {numOfCakes}</h3>
<h3>Name of cakes - {nameOfCake}</h3>
<button onClick={() => {updateName('best cake')}}>change cake's name</button>
<button onClick={() => {updateNum(100)}}>change cake's number</button>
</div>
);
};
這樣就達(dá)成我們的目標(biāo)了。
總結(jié)
核心代碼如下:
type GetArrFirst<T> = T extends [infer Res, ...infer P] ? Res : unknown;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useCreateReduxFunction = <S extends Slice>(slice: S) => {
return <T extends keyof S['actions']>(name: T) => {
const dispatch = useAppDispatch();
const actionCreator = (slice.actions as S['actions'])[name];
return (payload: GetArrFirst<Parameters<typeof actionCreator>>) => {
dispatch(actionCreator(payload));
};
};
};
經(jīng)過(guò)上面的優(yōu)化方案,我們封裝了 useAppSelector 、 useAppDispatch 和 useCreateReduxFunction三個(gè) hook,解決了目前使用 Redux-Toolkit 還會(huì)存在的一些繁瑣點(diǎn)。
以上就是React TypeScript 應(yīng)用中便捷使用Redux Toolkit方法詳解的詳細(xì)內(nèi)容,更多關(guān)于React TypeScript使用Redux Toolkit的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
react-router-dom6(對(duì)比?router5)快速入門指南
這篇文章主要介紹了快速上手react-router-dom6(對(duì)比?router5),通過(guò)本文學(xué)習(xí)最新的react-router-dom?v6版本的路由知識(shí),并且會(huì)與v5老版本進(jìn)行一些對(duì)比,需要的朋友可以參考下2022-08-08
react項(xiàng)目從新建到部署的實(shí)現(xiàn)示例
這篇文章主要介紹了react項(xiàng)目從新建到部署的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
基于React Native 0.52實(shí)現(xiàn)輪播圖效果
這篇文章主要為大家詳細(xì)介紹了基于React Native 0.52實(shí)現(xiàn)輪播圖效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-11-11
React為什么需要Scheduler調(diào)度器原理詳解
這篇文章主要為大家介紹了React為什么需要Scheduler調(diào)度器原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
在react-router4中進(jìn)行代碼拆分的方法(基于webpack)
這篇文章主要介紹了在react-router4中進(jìn)行代碼拆分的方法(基于webpack),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
React?Hooks之useDeferredValue鉤子用法示例詳解
useDeferredValue鉤子的主要目的是在React的并發(fā)模式中提供更流暢的用戶體驗(yàn),特別是在有高優(yōu)先級(jí)和低優(yōu)先級(jí)更新的情況下,本文主要講解一些常見(jiàn)的使用場(chǎng)景及其示例2023-09-09
React將組件作為參數(shù)進(jìn)行傳遞的3種方法實(shí)例
其實(shí)react組件之間傳遞參數(shù)是比較簡(jiǎn)單的,組件傳入?yún)?shù)的一種方式,下面這篇文章主要給大家介紹了關(guān)于React將組件作為參數(shù)進(jìn)行傳遞的3種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
基于Cloud?Studio構(gòu)建React完成點(diǎn)餐H5頁(yè)面(騰訊云?Cloud?Studio?實(shí)戰(zhàn)訓(xùn)練營(yíng))
最近也是有機(jī)會(huì)參與到了騰訊云舉辦的騰訊云Cloud Studio實(shí)戰(zhàn)訓(xùn)練營(yíng),借此了解了騰訊云Cloud?Studio產(chǎn)品,下面就來(lái)使用騰訊云Cloud?Studio做一個(gè)實(shí)戰(zhàn)案例來(lái)深入了解該產(chǎn)品的優(yōu)越性吧,感興趣的朋友跟隨小編一起看看吧2023-08-08

