useReducer
是React提供的一個(gè)高級(jí)Hook,它不像useEffect苏潜、useState、useRef等必須hook一樣,沒(méi)有它我們也可以正常完成需求的開(kāi)發(fā)蔼水,但useReducer可以使我們的代碼具有更好的可讀性萍启、可維護(hù)性、可預(yù)測(cè)性肋杖。
下面我們會(huì)分三篇文章詳細(xì)介紹如何在項(xiàng)目中使用useReducer
:
- 第一篇:主要介紹JavaScript中
reducer
的概念以及它的特點(diǎn)溉仑,對(duì)reducer、redux等比較熟悉的小伙伴可以跳過(guò)本篇 - 第二篇:主要介紹
useReducer
的使用方式和它的場(chǎng)景状植,以及useReducer帶來(lái)的好處 - 第三篇:會(huì)進(jìn)一步介紹復(fù)雜項(xiàng)目浊竟、復(fù)雜頁(yè)面中的useReducer使用
什么是reducer
reducer
的概念是伴隨著Redux的出現(xiàn)逐漸在JavaScript中流行起來(lái)怨喘。但我們并不需要學(xué)習(xí)Redux去了解Reducer。簡(jiǎn)單來(lái)說(shuō) reducer是一個(gè)函數(shù)(state, action) => newState
:接收當(dāng)前應(yīng)用的state和觸發(fā)的動(dòng)作action振定,計(jì)算并返回最新的state必怜。下面是一段偽代碼:
// 舉個(gè)栗子 計(jì)算器reducer,根據(jù)state(當(dāng)前狀態(tài))和action(觸發(fā)的動(dòng)作加后频、減)參數(shù)梳庆,計(jì)算返回newState
function countReducer(state, action) {
switch(action.type) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}
上面例子:state是一個(gè)number類型的數(shù)值,reducer根據(jù)action的類型(加卑惜、減)對(duì)應(yīng)的修改state膏执,并返回最終的state。為了剛接觸到reducer
概念的小伙伴更容易理解,可以將state改為count露久,但請(qǐng)始終牢記count仍然是state更米。
function countReducer(count, action) {
switch(action.type) {
case 'add':
return count + 1;
case 'sub':
return count - 1;
default:
return count;
}
}
reducer 的冪等性
從上面的示例可以看到reducer
本質(zhì)是一個(gè)純函數(shù),沒(méi)有任何UI和副作用抱环。這意味著相同的輸入(state壳快、action),reducer函數(shù)無(wú)論執(zhí)行多少遍始終會(huì)返回相同的輸出(newState)镇草。因此通過(guò)reducer函數(shù)很容易推測(cè)state的變化眶痰,并且也更加容易單元測(cè)試。
expect(countReducer(1, { type: 'add' })).equal(2); // 成功
expect(countReducer(1, { type: 'add' })).equal(2); // 成功
expect(countReducer(1, { type: 'sub' })).equal(0); // 成功
state 和 newState 的理解
state
是當(dāng)前應(yīng)用狀態(tài)對(duì)象梯啤,可以理解就是我們熟知的React里面的state竖伯。
在上面的例子中state是一個(gè)基礎(chǔ)數(shù)據(jù)類型,但很多時(shí)候state可能會(huì)是一個(gè)復(fù)雜的JavaScript對(duì)象因宇,如上例中count有可能只是 state中的一個(gè)屬性七婴。針對(duì)這種場(chǎng)景我們可以使用ES6的結(jié)構(gòu)賦值:
// 返回一個(gè) newState (newObject)
function countReducer(state, action) {
switch(action.type) {
case 'add':
return { ...state, count: state.count + 1; }
case 'sub':
return { ...state, count: state.count - 1; }
default:
return count;
}
}
關(guān)于上面這段代碼有兩個(gè)重要的點(diǎn)需要我們記住:
reducer處理的state對(duì)象必須是
immutable
察滑,這意味著永遠(yuǎn)不要直接修改參數(shù)中的state對(duì)象打厘,reducer函數(shù)應(yīng)該每次都返回一個(gè)新的state object既然reducer要求每次都返回一個(gè)新的對(duì)象,我們可以使用ES6中的解構(gòu)賦值方式去創(chuàng)建一個(gè)新對(duì)象贺辰,并復(fù)寫(xiě)我們需要改變的state屬性户盯,如上例。
看上去很完美饲化,但如果我們的state是多層嵌套莽鸭,解構(gòu)賦值實(shí)現(xiàn)就非常復(fù)雜:
function bookReducer(state, action) {
switch(action.type) {
// 添加一本書(shū)
case 'addBook':
return {
...state,
books: {
...state.books,
[bookId]: book,
}
};
case 'sub':
// ....
default:
return state;
}
}
對(duì)于這種復(fù)雜state的場(chǎng)景推薦使用immer等immutable庫(kù)解決。
state為什么需要immutable吃靠?
- reducer的冪等性
我們上文提到過(guò)reducer需要保持冪等性硫眨,更加可預(yù)測(cè)、可測(cè)試巢块。如果每次返回同一個(gè)state礁阁,就無(wú)法保證無(wú)論執(zhí)行多少次都是相同的結(jié)果
- React中的state比較方案
React在比較oldState
和newState
的時(shí)候是使用Object.is函數(shù)巧号,如果是同一個(gè)對(duì)象則不會(huì)出發(fā)組件的rerender。
可以參考官方文檔bailing-out-of-a-dispatch氮兵。
action 理解
action:用來(lái)表示觸發(fā)的行為裂逐。
- 用type來(lái)表示具體的行為類型(登錄、登出泣栈、添加用戶卜高、刪除用戶等)
- 用payload攜帶的數(shù)據(jù)(如增加書(shū)籍,可以攜帶具體的book信息)南片,我們用上面addBook的action為例:
const action = {
type: 'addBook',
payload: {
book: {
bookId,
bookName,
author,
}
}
}
function bookReducer(state, action) {
switch(action.type) {
// 添加一本書(shū)
case 'addBook':
const { book } = action.payload;
return {
...state,
books: {
...state.books,
[book.bookId]: book,
}
};
case 'sub':
// ....
default:
return state;
}
}
總結(jié)
至此基本介紹完了reducer相關(guān)的內(nèi)容掺涛,簡(jiǎn)單總結(jié)一下:reducer
是一個(gè)利用action
提供的信息,將state
從A轉(zhuǎn)換到B的一個(gè)純函數(shù)疼进,具有一下幾個(gè)特點(diǎn):
- 語(yǔ)法:(state, action) => newState
- Immutable:每次都返回一個(gè)newState薪缆, 永遠(yuǎn)不要直接修改state對(duì)象
- Action:一個(gè)常規(guī)的Action對(duì)象通常有type和payload(可選)組成
- type: 本次操作的類型,也是 reducer 條件判斷的依據(jù)
- payload: 提供操作附帶的數(shù)據(jù)信息
下篇文章我們會(huì)進(jìn)入正題:如何使用useReducer簡(jiǎn)化我們的state管理伞广。
最后慣例拣帽,歡迎大家star我們的人人貸大前端團(tuán)隊(duì)博客,所有的文章還會(huì)同步更新到知乎專欄 和 掘金賬號(hào)嚼锄,我們每周都會(huì)分享幾篇高質(zhì)量的大前端技術(shù)文章减拭。如果你喜歡這篇文章,希望能動(dòng)動(dòng)小手給個(gè)贊区丑。
參考資料
- https://github.com/immerjs/immer
- https://reactjs.org/docs/context.html
- https://reactjs.org/docs/hooks-faq.html
- https://www.robinwieruch.de/react-usereducer-vs-usestate/
- https://www.robinwieruch.de/react-state-usereducer-usestate-usecontext/
- https://kentcdodds.com/blog/application-state-management-with-react