Redux Toolkit 是個(gè)好東西
前言
hello 朋友們,我又來分享技術(shù)調(diào)研了,蕪湖~
這次是 Readux Toolkit,它配合我上次調(diào)研的 proxy-memoize 一起來優(yōu)化一下我們項(xiàng)目現(xiàn)在的狀態(tài)管理。
相信大部分小伙伴還是對(duì) redux 更了解,那這個(gè)Readux Toolkit又是個(gè)啥東西的,能帶來啥,怎么用。那這篇文章可能能幫你解決這幾個(gè)疑問。當(dāng)然如果你想更詳細(xì)的了解的話肯定是要看官網(wǎng)的啦。
那么話不多說,進(jìn)入正題吧
基于Redux優(yōu)化
首先毫無疑問,Readux Toolkit是基于Redux的一系列優(yōu)化,那優(yōu)化了redux的什么呢,這里我就簡(jiǎn)要的講一講redux可能存在的缺點(diǎn)(從一些角度說它的缺點(diǎn)也是了優(yōu)點(diǎn),因場(chǎng)景而異啦):
- 我們很多狀態(tài)都要抽象到 store,一個(gè)變化就要對(duì)應(yīng)編寫 action,reducer
- 需要幾個(gè)軟件包來使Redux與React一起工作,例如redux-thunk、reselect
- Redux的一些理念導(dǎo)致我們需要寫很多樣板代碼
- 配置 Redux store太復(fù)雜
當(dāng)然我們也不全是因?yàn)閞edux他的這些所謂的缺點(diǎn),而非要卷來優(yōu)化哈,其實(shí)也是因?yàn)檎{(diào)研Readux Toolkit我才發(fā)現(xiàn)這redux的缺點(diǎn),雖然是上級(jí)給的任務(wù),但是調(diào)研之后發(fā)現(xiàn)還是真香。
需了解的知識(shí)
首先當(dāng)然是要了解redux知識(shí)啦,有redux知識(shí)為了方便更迅速理解Readux Toolkit的實(shí)現(xiàn)或者他的妙用,還需要先了解他的核心依賴:
- immer
- redux
- redux-thunk
- reselect
immer
這幾個(gè)中我是不了解這個(gè)immer的,其他基本略知一二,那么就看看這個(gè)immer庫是個(gè)啥吧:
這個(gè)庫,它允許我們把state的 不變的(immutable) 特性轉(zhuǎn)化為 可變的(mutable);
具體上的實(shí)現(xiàn)它是利用了 proxy,當(dāng)我們對(duì) state 進(jìn)行修改,proxy對(duì)象會(huì)攔截,并且按順序替換上層對(duì)象,返回的新對(duì)象??瓷先ゾ秃孟褡詣?dòng)幫你直接修改了state
api
首先看看整體的Api,然后再詳細(xì)說說可能會(huì)常用的:
configureStore (): 包裝createStore以提供簡(jiǎn)化的配置選項(xiàng)和良好的默認(rèn)設(shè)置。它可以自動(dòng)組合你的slice reducers,添加你提供的任何 Redux 中間件,默認(rèn)包括Redux-thunk,并啟用 Redux DevTools 擴(kuò)展。createReducer (): 它允許您為 case reducer 函數(shù)提供一個(gè)動(dòng)作類型查找表,而不是編寫 switch 語句。此外,它還自動(dòng)使用immer庫,讓您使用普通的可變代碼編寫更簡(jiǎn)單的不可變更新,比如 state.todos [3].complete = true。createAction (): 為給定的動(dòng)作類型字符串生成動(dòng)作創(chuàng)建器函數(shù)。函數(shù)本身定義了toString (),因此可以使用它來代替類型常量。createSlice (): 接受 reducer 函數(shù)的對(duì)象、片名和初始狀態(tài)值,并自動(dòng)生成帶有相應(yīng)動(dòng)作創(chuàng)建器和動(dòng)作類型的 slice reducer。createAsyncThunk: 接受一個(gè)操作類型字符串和一個(gè)返回promise函數(shù),并生成一個(gè) thunk,該 thunk 根據(jù)該promise dispatchespending/fulfilled/rejected的action typescreateEntityAdapter: 生成一組可重用的還原器和選擇器來管理存儲(chǔ)中的規(guī)范化數(shù)據(jù)- reselect庫中的
createSelectorutility,為了方便使用而re-exported。
configureStore
step1 是 configureStore,這個(gè)必不可少,用來創(chuàng)建一個(gè)空的Redux store,同時(shí)這里呢會(huì)自動(dòng)配置 Redux DevTools 擴(kuò)展,以便檢查存儲(chǔ):
import?{?configureStore?}?from?'@reduxjs/toolkit'
export?const?store?=?configureStore({
????reducer:?{},
})
step2 是要< provider > 來使 redux 對(duì) React 組件可用,將導(dǎo)出的store當(dāng)作prop傳遞給它,這一塊不必多說
createSlice
step3 這里會(huì)有點(diǎn)不一樣了,我們要通過 createSlice 創(chuàng)建一個(gè)Redux狀態(tài)切片(Redux State Slice),創(chuàng)建這個(gè)slice需要:
- 一個(gè)字符串名來標(biāo)識(shí)該片
- 一個(gè)初始狀態(tài)值
- 一個(gè)或多個(gè) reducer 函數(shù)來定義如何更新該狀態(tài) 創(chuàng)建這個(gè)slice能干嘛?可以導(dǎo)出生成的 Redux 動(dòng)作創(chuàng)建器和整個(gè)片的 reducer 函數(shù):
import?{?createSlice?}?from?'@reduxjs/toolkit'
const?initialState?=?{
??value:?0,
}
export?const?counterSlice?=?createSlice({
??name:?'counter',
??initialState,
??reducers:?{
????increment:?(state)?=>?{
??????/**?
???????* Redux Toolkit 允許我們?cè)谶€原器中編寫“可變的(mutable)”邏輯。
???????*?它實(shí)際上并沒有改變狀態(tài),因?yàn)樗褂?Immer?庫,
???????*?它將檢測(cè)對(duì)"draft?state"?的更改,并根據(jù)這些更改生成
???????*?一個(gè)全新的不可變狀態(tài)
???????*/
??????state.value?+=?1
????},
????decrement:?(state)?=>?{
??????state.value?-=?1
????},
????incrementByAmount:?(state,?action)?=>?{
??????state.value?+=?action.payload
????},
??},
})
//?為每個(gè)?reducer?函數(shù)生成動(dòng)作創(chuàng)建器
export?const?{?increment,?decrement,?incrementByAmount?}?=?counterSlice.actions
export?default?counterSlice.reducer
結(jié)合這個(gè)例子,可以清楚的看到這個(gè)createSlice接收的:一個(gè)字符串名來標(biāo)識(shí)該片也就是name,一個(gè)初始狀態(tài)值initialState,以及多個(gè)reducer行數(shù)。并且為每個(gè) reducer 函數(shù)生成動(dòng)作創(chuàng)建器。
它有啥作用或者其他好處呢?可能一小部分人不看代碼,我把注釋給拿下來。
我們知道 Redux 它是要求我們通過制作數(shù)據(jù)副本和更新副本來編寫所有狀態(tài)更新的。然而, createSlice 和 createReducer 在內(nèi)部使用 Immer 來允許我們編寫“可變的(mutable)”的更新邏輯,使其成為正確的不可變更的更新。
Redux Toolkit 允許我們?cè)谶€原器中編寫“mutable”邏輯。它實(shí)際上并沒有改變狀態(tài),因?yàn)樗褂?Immer 庫,檢測(cè)對(duì)“draft state”的更改,并根據(jù)這些更改生成一個(gè)全新的不可變狀態(tài)
step 4 ?我們需要從上面的創(chuàng)建的空的 store 導(dǎo)入 reducer 函數(shù)并將其添加到我們的存儲(chǔ)中,通過在 reducer 參數(shù)中定義一個(gè)字段,告訴 store 使用這個(gè) slice reducer 函數(shù)來處理該狀態(tài)的所有更新。
import?{?configureStore?}?from?'@reduxjs/toolkit'
import?counterReducer?from?'../features/counter/counterSlice'
export?default?configureStore({
??reducer:?{
????counter:?counterReducer,
??},
})
step 5 現(xiàn)在我們可以使用 React-Redux hook 讓 React 組件與 Redux 存儲(chǔ)交互。我們可以使用 useSelector 從存儲(chǔ)中讀取數(shù)據(jù),并使用 useDispatch 分派操作。
理解的話我們看這個(gè) 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>
??)
}
當(dāng)點(diǎn)擊+、-按鈕時(shí)的動(dòng)作,分析:
- 相應(yīng)的 Redux action 將被派發(fā)(dispatched)到存儲(chǔ)區(qū)(store)
- 這個(gè) counter slice reducer將觀測(cè)actions并更新其狀態(tài)
- < Counter > 組件將觀測(cè)到存儲(chǔ)(store)中新的狀態(tài)值,并使用新數(shù)據(jù)re-render自己
例子
這里也放一個(gè)簡(jiǎn)單的例子,可以訪問codesandbox的可以戳這里,也可以去官網(wǎng)找這個(gè)例子。
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')
);
總結(jié)
這里簡(jiǎn)要的講一下這個(gè)簡(jiǎn)單例子的整體的步驟:
- 使用 configureStore 創(chuàng)建 Redux 存儲(chǔ)
- configureStore 接受作為命名參數(shù)的 reducer 函數(shù)
- configureStore 自動(dòng)設(shè)置好了默認(rèn)設(shè)置
- 在 組件外包裹 React-Redux < Provider > 組件
- < Provider store = { store } >
- 使用字符串名稱、初始 state 和 reducer 函數(shù)調(diào)用 createSlice
- Reducer 函數(shù)可能使用 Immer“變異(mutate)”狀態(tài)
- 導(dǎo)出生成的slice reducer 和 action creators
- 使用 useSelector 鉤子從 store 中讀取數(shù)據(jù)
- 使用 useDispatch 鉤子獲取 dispatch 函數(shù),并根據(jù)需要進(jìn)行 dispatch actions 操作
OK,大概就總結(jié)道這里了,你會(huì)發(fā)現(xiàn)還有一些主要的api沒有講到,比如很重要的createReducer 和 createAction這些還沒講,但是這個(gè)小應(yīng)用也能實(shí)現(xiàn)了(這個(gè)例子的場(chǎng)景限制發(fā)揮了呀)。
那其實(shí)你知道這些基本就能使用了,還有就是這篇也沒講到 use Redux Toolkit and React-Redux with TypeScript,下篇我們?cè)敿?xì)講一下搭配 TypeScript 如何使用以及他的好處吧。
??????????
非常感謝你看到這,如果覺得不錯(cuò)的話點(diǎn)個(gè)贊 ? 吧
今天也是在努力變強(qiáng)不變禿的 HearLing 呀 ??
??????????
