React自定義Hook
本文適合覺得React自定義hook難、對(duì)自定義hook有興趣的小伙伴閱讀。
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~
一、前言
React官方文檔:
https://zh-hans.reactjs.org/docs/hooks-custom.html#gatsby-focus-wrapper
二、什么是自定義Hook
自定義 Hook 是一個(gè)函數(shù),其名稱以 “use” 開頭,函數(shù)內(nèi)部可以調(diào)用其他的 Hook。
????以上是React官方的定義,我們可以很明確,自定義一個(gè)Hook就是開發(fā)一個(gè)函數(shù),這個(gè)函數(shù)只是一個(gè)普通的函數(shù),不是什么構(gòu)造函數(shù)、自執(zhí)行函數(shù)之類的。
那要開發(fā)一個(gè)什么樣的函數(shù)?一個(gè)函數(shù)的組成有函數(shù)名、參數(shù)、函數(shù)內(nèi)部、返回值,我們先結(jié)合React官方文檔中對(duì)自定義Hook的要求,對(duì)其每個(gè)部分分析一下。
函數(shù)名:以?“
use” 開頭,比如useState。為什么要以 “use” 開頭。這是因?yàn)椋绻灰?“use” 開頭的話,無法判斷這個(gè)函數(shù)是否包含對(duì)其內(nèi)部 Hook 的調(diào)用,React 將無法自動(dòng)檢查這個(gè)自定義Hook是否違反了 Hook 的規(guī)則。參數(shù):并沒有特殊要求,只是React官網(wǎng)中有提到過,可以接收另一個(gè)Hook的返回值作為參數(shù),實(shí)現(xiàn)在多個(gè) Hook 之間傳遞信息的功能。
函數(shù)內(nèi)部:在函數(shù)內(nèi)部可以調(diào)用其他的Hook,但是要遵循兩個(gè)規(guī)則:
只在最頂層使用 Hook ,不要在循環(huán),條件或嵌套函數(shù)中調(diào)用 Hook,這是因?yàn)?React 是靠 Hook 調(diào)用的順序來知道哪個(gè) state 對(duì)于哪個(gè)?
useState?的。具有看官網(wǎng)這里的解釋。不要在普通的 JavaScript 函數(shù)中調(diào)用 Hook,只能在 React 函數(shù)中調(diào)用 Hook。這一點(diǎn)很好理解吧,Hook 本身就是由 React 提供的。
返回值:沒有限制一定要返回什么,當(dāng)然一個(gè)函數(shù)默認(rèn)返回一個(gè)
undefined。
三、自定義Hook的場(chǎng)景
四、自定義一個(gè)最簡(jiǎn)單的Hook
const?Demo?=?()?=>{
??useEffect(()?=>?{
?????console.log('組件首次渲染')
??},[]);
}
export?default?Demo;
useEffect的第二參數(shù)接收一個(gè)空數(shù)組表示只在組件首次渲染時(shí)執(zhí)行作為第一參數(shù)傳入useEffect的方法。那我們把這個(gè)通用邏輯通過自定義一個(gè)名叫useMount的 Hook 提取出來,實(shí)現(xiàn)如下所示:import?{?useEffect?}?from?'react';
const?useMount?=?(fn:?()?=>?void)?=>?{
??useEffect(()?=>?{
????fn();
??},?[]);
};
export?default?useMount;
useMount這個(gè)自定義Hook接收函數(shù)fn作為參數(shù),在組件首次渲染時(shí)執(zhí)行函數(shù)fn。五、如何使用自定義Hook
export導(dǎo)出,故用import引入使用:import?useMount?from?'@/hooks/useMount';
const?Demo?=?()?=>{
??useMount(()?=>{
?????console.log('組件首次渲染')
??})
??return(
????<div>demodiv>
??)
}
export?default?Demo;六、自定義Hook的內(nèi)部
? ? React提供了10個(gè)內(nèi)部Hook,在自定義Hook的內(nèi)部,一般都是利用這10個(gè)內(nèi)部Hook,進(jìn)行組裝和擴(kuò)展,來自定義各種功能的Hook。
初學(xué)者在這里往往有個(gè)疑問,比如在一個(gè)自定義Hook?useMyState中使用useState定義了一個(gè)a變量。
import?{?useState?}?from?'react';
const?useMyState?=?()?=>?{
?const?[a,setA]?=?useState();
?return?[a,setA]
};
export?default?useMyState;
useMy的組件中又使用useState定義了一個(gè)a變量,這樣會(huì)不會(huì)引起沖突。import?useMyState?from?'@/hooks/useMount';
const?Demo?=?()?=>{
??const?[a,setA]?=?useState();
??const?[b,setB]?=?useMyState();
??return(
????<div>demodiv>
??)
}
export?default?Demo;
useState和useEffect,它們是完全獨(dú)立的。而且Hook也是個(gè)函數(shù),有函數(shù)作用域在兜底。const?Demo?=?()?=>{
??const?[current?,?setCurrent?]?=?useState(1);
??const?[previous?,?setPrevious?]=?useState(0);
??const?updata?=?()?=>?{
????setCurrent(value?=>{
??????setPrevious(value);
??????return?value+1;
????})
??}
??return?(
????<div>
??????<div>{current}div>
??????<div>{previous}div>
??????<div?onClick={updata}>改變div>
????div>
??);
}
export?default?Demo
其中使用兩個(gè)useState創(chuàng)建了當(dāng)前current和之前previous的兩個(gè)變量,在改變current時(shí),把current之前的值賦值給previous。雖然這么實(shí)現(xiàn)也可以,但是有點(diǎn)不優(yōu)雅,假如還要對(duì)current之前的值進(jìn)行一些判斷再賦值給
previous,那是不是在改變current的setCurrent方法中要寫很多不相干的東西,有點(diǎn)違背單一原則。
usePrevious。import?{?useRef?}?from?'react';
const?usePrevious?=?(state,?compare)?=>{
??const?prevRef?=?useRef();
??const?curRef?=?useRef();
??const?needUpdate?=?typeof?compare?===?'function'???compare(curRef.current,?state)?:?true;
??if?(needUpdate)?{
????prevRef.current?=?curRef.current;
????curRef.current?=?state;
??}
??return?prevRef.current;
}
export?default?usePrevious;useRef來保存一個(gè)值的舊值和新值,比之前用useState來保存好。原因在于useRef返回一個(gè)可變的?ref對(duì)象,其current屬性被初始化為傳入的參數(shù)。返回的ref對(duì)象在組件的整個(gè)生命周期內(nèi)保持不變。usePrevious呢,示例如下所示:import?usePrevious?from?'@/hooks/usePrevious';
const?Demo?=?()?=>{
??const?[current?,?setCurrent?]?=?useState(1);
??const?compare?=?(oldValue,newValue)?=>{
?????if(oldValue?!==?newValue){
???????return?true;
?????}
??}
??const?previous?=?usePrevious(current,compare);
??const?updata?=?()?=>?{
????setCurrent(value?=>{
??????value?=?value?+?1;
??????return?value;
????})
??}
??return?(
????<div>
??????<div>{current}div>
??????<div>{previous}div>
??????<div?onClick={updata}>改變div>
????div>
??);
}
export?default?Demo
七、自定義Hook和React內(nèi)部Hook有必然關(guān)系嗎
? ? 到這里,我們?cè)偎伎家粋€(gè)問題,一定要用React內(nèi)部Hook開發(fā)自定義Hook嗎?
答案當(dāng)然是不一定了。為什么呢?我們?cè)賮砜匆幌?/span>usePrevious的使用,usePrevious并沒有像useState提供更新值的方法,那為什么usePrevious的返回值previous會(huì)實(shí)時(shí)更新。
這就要說到函數(shù)式組件的特性了,函數(shù)式組件的函數(shù)體就是類組件中的render(),當(dāng)組件的state或props改變時(shí),組件會(huì)重新渲染,函數(shù)式組件的函數(shù)體會(huì)重新執(zhí)行一遍。
那么當(dāng)current改變時(shí),Demo這個(gè)函數(shù)會(huì)重新執(zhí)行一遍,執(zhí)行到const previous = usePrevious(current,compare);,usePrevious接收到新的current,自然返回新的previous,這樣就實(shí)時(shí)更新了。
好那么現(xiàn)在自定義一個(gè)Hook?useMyCount?如下所示:
const?useMyCount?=?(state)?=>{
??return?state?+?1;
}
export?default?useMyCount;
import?useMyCount?from?'@/hooks/usePrevious';
const?Demo?=?()?=>{
??const?[num?,?setNum?]?=?useState(1);
??const?count?=?useMyCount(num);
??const?updata?=?()?=>?{
????setNum(value?=>{
??????value?=?value?+?1;
??????return?value;
????})
??}
??return?(
????<div>
??????<div>{num}div>
??????<div>{count}div>
??????<div?onClick={updata}>加一div>
????div>
??);
}
export?default?Demo
會(huì)發(fā)現(xiàn)Demo組件中count也會(huì)實(shí)時(shí)更新,而在自定義Hook?useMyCount中,有沒有使用React內(nèi)部Hook沒有絲毫關(guān)系。
再比如我自定義的一個(gè)獲取當(dāng)前時(shí)間戳的Hook:
const?useTime?=?()?=>{
??return?new?Date().getTime();
}
export?default?useTime;
import?*?as?Api?from?'@/api'
const?useTask?=?async?()?=>{
??const?res?=?await?Api.getTask();
??return?res;
}
export?default?useTask;
? 這些自定義Hook內(nèi)部都沒有用到React的內(nèi)部Hook,所以說自定義Hook不難。
八、關(guān)于自定義Hook一些要求? ?
? ? 自定義Hook相當(dāng)寫一個(gè)函數(shù),如果其內(nèi)部要使用React的內(nèi)部Hook,要遵循Hook的使用規(guī)則外。
此外還要符合書寫函數(shù)的一些要求,比如對(duì)參數(shù)的定義,對(duì)返回值結(jié)構(gòu)的要求。
? ? 最后要注意最重要的一點(diǎn),一個(gè)Hook只負(fù)責(zé)一件事情,即遵循單一原則。
參數(shù)方面的要求
? 無參數(shù)
? 允許 Hooks 無參數(shù)。
const?time?=?useTime();
單參數(shù)
單參數(shù)無論是否必填直接輸入。
const?a?=?useA(parame);
多必選參數(shù)
必選參數(shù)小于 2 個(gè),應(yīng)平級(jí)輸入。
const?a?=?useA(parame1,?parame2);
如果多于 2 個(gè),應(yīng)以 object 形式輸入。
const?a?=?useA(?{parame1:1,?parame2:2,?parame3:3?}?);
多非必選參數(shù)
多非必選參數(shù)以 object 形式輸入。
const?a?=?useA({parame1?,?parame2?,?parame3?,?parame4?});
必選參數(shù) + 非必選參數(shù)
必選參數(shù)在前,非必選參數(shù)在后。
const?a?=?useA(parame,{parame1?,?parame2?,?parame3?,?parame4?});
返回值結(jié)構(gòu)的要求
無輸出
允許 Hooks 無輸出,一般常見于生命周期類 Hooks。
useMount(()?=>?{});
value 型
Hooks 輸出僅有一個(gè)值。
const?a?=?useA();
value setValue 型
輸出值為 value 和 setValue 類型的,結(jié)構(gòu)為 [value, setValue] 。
const?[state,?setState]?=?useA(a);
value actions 型
其中actions為操作數(shù)據(jù)的方法。
輸出值為單 value 與多 actions 類型的,結(jié)構(gòu)為 [value, actions] 。
const?[value,?{?actions1,?actions2,?actions3}]?=?useA(...);
values 型
輸出值為多 value 類型的,結(jié)構(gòu)為 {...values}
const?{value1,?value2,?value3}?=?useA();
values actions 型
輸出值為多 value 與多 actions 類型的,結(jié)構(gòu)為 {...values, ...actions} 。
const?{value1,?value2,?actions1,?actions2}?=?useA(...);本文來自作者@紅塵煉心
https://juejin.cn/post/7022777747722207269
九、總結(jié)
關(guān)注我,一起攜手進(jìn)階
歡迎關(guān)注前端早茶,與廣東靚仔攜手共同進(jìn)階~
