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