詳解如何構(gòu)建自己的react hooks
1. 常用的一個(gè) hooks
官方中提供了幾個(gè)內(nèi)置的鉤子,我們簡(jiǎn)單了解下他們的用法。
1.1 useState: 狀態(tài)鉤子
需要更新頁(yè)面狀態(tài)的數(shù)據(jù),我們可以把他放到 useState 的鉤子里。例如點(diǎn)擊按鈕一下,數(shù)據(jù)加 1 的操作:
const [count, setCount] = useState(0); return (<> <p>{ count}</p> <button onClick = { () => setCount(count + 1) }> add 1 </button> </> );
在 typescript 的體系中,count 的類(lèi)型,默認(rèn)就是當(dāng)前初始值的類(lèi)型,例如上面例子中的變量就是 number
類(lèi)型。如果我們想自定義這個(gè)變量的類(lèi)型,可以在 useState 后面進(jìn)行定義:
const [count, setCount] = useState<number | null>(null); // 變量count為number類(lèi)型或者null類(lèi)型
同時(shí),使用 useState 改變狀態(tài)時(shí),是整個(gè)把 state 替換掉的,因此,若狀態(tài)變量是個(gè) object 類(lèi)型的數(shù)據(jù),我只想修改其中的某個(gè)字段,在之前 class 組件內(nèi)調(diào)用 setState 時(shí),他內(nèi)部會(huì)自動(dòng)合并數(shù)據(jù)。
class Home extends React.Component { state = { name: 'wenzi', age: 20, score: 89 }; update() { this.setState({ score: 98 }); // 內(nèi)部自動(dòng)合并 } }
但在 function 組件內(nèi)使用 useState 時(shí),需要自己先合并數(shù)據(jù),然后再調(diào)用方法,否則會(huì)造成字段的丟失。
const [person, setPerson] = useState({ name: 'wenzi', age: 20, score: 89 }); setPerson({ ...person, { score: 98 } }); // 先合并數(shù)據(jù) { name: 'wenzi', age: 20, score: 98 } setPerson({ score: 98 }); // 僅傳入要修改的字段,后name和age字段丟失
1.2 useEffect: 副作用鉤子
useEffect 可以看做是 componentDidMount,componentDidUpdate 和 componentWillUnmount 這三個(gè)函數(shù)的組合。
useEffect 鉤子在組件初始化完畢時(shí),一定會(huì)執(zhí)行一次,在組件重新渲染的過(guò)程中,是否還要 update,還要看傳入的第 2 個(gè)參數(shù)。
- 當(dāng)只有回調(diào)函數(shù)這一個(gè)參數(shù)時(shí),組件的每次更新,回調(diào)都會(huì)執(zhí)行;
- 當(dāng)有 2 個(gè)參數(shù)時(shí),只有第 2 參數(shù)里的數(shù)據(jù)發(fā)生變化時(shí),回調(diào)才執(zhí)行;
- 只想在組件初始化完畢時(shí)只執(zhí)行一次,第 2 個(gè)參數(shù)可以傳入一個(gè)空的數(shù)組;
我們可以看下這個(gè)例子,無(wú)論點(diǎn)擊 add按鈕 還是 settime按鈕 ,useEffect 的回調(diào)都會(huì)執(zhí)行:
const Home = () => { const [count, setCount] = useState(0); const [nowtime, setNowtime] = useState(0); useEffect(() => { console.log('count', count); console.log('nowtime', nowtime); }); return ( <> <p>count: {count} </p> <p>nowtime: {nowtime} </p> <button onClick = {() => setCount(count + 1)}> add 1 </button> <button onClick = {() => setNowtime(Date.now())} > set now time </button> </>); };
若改成下面的這樣,回調(diào)僅會(huì)在 count 發(fā)生變化時(shí)才會(huì)在控制臺(tái)輸出,僅修改 nowtime 的值時(shí)沒(méi)有輸出:
useEffect(() => { console.log('count', count); console.log('nowtime', nowtime); }, [count]);
useEffect 的回調(diào)函數(shù)還可以返回一個(gè)函數(shù),這個(gè)函數(shù)在 effect 生命周期結(jié)束之前調(diào)用。為防止內(nèi)存泄漏,清除函數(shù)會(huì)在組件卸載前執(zhí)行。另外,如果組件多次渲染,則在執(zhí)行下一個(gè) effect 之前,上一個(gè) effect 就已被清除。
基于上面的代碼,我們稍微修改一下:
useEffect(() => { console.log('count', count); console.log('nowtime', nowtime); return () => console.log('effect callback will be cleared'); }, [count]);
基于這個(gè)機(jī)制,在一些存在添加綁定和取消綁定的案例上特別合適,例如監(jiān)聽(tīng)頁(yè)面的窗口大小變化、設(shè)置定時(shí)器、與后端的 websocket 接口建立連接和斷開(kāi)連接等,都可以預(yù)計(jì) useEffect 進(jìn)行二次的封裝,形成自定義的 hook。關(guān)于自定義 hook,下面我們會(huì)講到。
1.3 useMemo 和 useCallback
function 組件中定義的變量和方法,在組件重新渲染時(shí),都會(huì)重新重新進(jìn)行計(jì)算,例如下面的這個(gè)例子:
const Home = () => { const [count, setCount] = useState(0); const [nowtime, setNowtime] = useState(0); const getSum = () => { const sum = ((1 + count) * count) / 2; return sum + ' , ' + Math.random(); // 這個(gè)random是為了看到區(qū)別 }; return ( <> <p> count: {count}< /p> <p> sum: {getSum()}</p> <p> nowtime: {nowtime}</p> <button onClick = {() => setCount(count + 1)} > add 1 </button> <button onClick = {() => setNowtime(Date.now())}> set now time </button> </>); };
這里有 2 個(gè)按鈕,一個(gè)是 count+1,一個(gè)設(shè)置當(dāng)前的時(shí)間戳, getSun() 方法是計(jì)算從 1 到 count 的和,我們每次點(diǎn)擊 add 按鈕后,sum 方法都會(huì)重新計(jì)算和??墒钱?dāng)我們點(diǎn)擊 settime 按鈕時(shí),getSum 方法也會(huì)重新計(jì)算,這是沒(méi)有必要的。
這里我們可以使用 useMemo 來(lái)修改下:
const sum = useMemo(() => ((1 + count) * count) / 2 + ' , ' + Math.random(), [count]); <p> {sum} </p>;
修改后就可以看到,sum 的值只有在 count 發(fā)生變化的時(shí)候才重新計(jì)算,當(dāng)點(diǎn)擊 settime 按鈕的時(shí)候,sum 并沒(méi)有重新計(jì)算。這要得益于 useMemo 鉤子的特性:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
useMemo 返回回調(diào)里 return 的值,而且 memoizedValue 它僅會(huì)在某個(gè)依賴(lài)項(xiàng)改變時(shí)才重新計(jì)算。這種優(yōu)化有助于避免在每次渲染時(shí)都進(jìn)行高開(kāi)銷(xiāo)的計(jì)算。如果沒(méi)有提供依賴(lài)項(xiàng)數(shù)組,useMemo 在每次渲染時(shí)都會(huì)計(jì)算新的值。
在上面的例子里,只有 count 變量發(fā)生變化時(shí),才重新計(jì)算 sum,否則 sum 的值保持不變。
useCallback 與 useMemo 類(lèi)型,只不過(guò) useCallback 返回的是一個(gè)函數(shù),例如:
const fn = useCallback(() => { return ((1 + count) * count) / 2 + ' , ' + nowtime; }, [count]);
2. 實(shí)現(xiàn)幾個(gè)自定義的 hook
在官方文檔里,實(shí)現(xiàn)了好友的在線(xiàn)與離線(xiàn)功能。這里我們自己也學(xué)著實(shí)現(xiàn)幾個(gè) hook。
2.1 獲取窗口變化的寬高
我們通過(guò)監(jiān)聽(tīng)resize事件來(lái)獲取實(shí)時(shí)獲取window窗口的寬高,對(duì)這個(gè)方法進(jìn)行封裝后可以在生命周期結(jié)束前能自動(dòng)解綁resize事件:
const useWinResize = () => { const [size, setSize] = useState({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }); const resize = useCallback(() => { setSize({ width: document.documentElement.clientWidth, height: document.documentElement.clientHeight }) }, []) useEffect(() => { window.addEventListener('resize', resize); return () => window.removeEventListener('resize', resize); }, []); return size; }
使用起來(lái)也非常方便:
const Home = () => { const {width, height} = useWinResize(); return <div> <p>width: {width}</p> <p>height: {height}</p> </div>; };
2.2 定時(shí)器 useInterval
在前端中使用定時(shí)器時(shí),通常要在組件生命周期結(jié)束前清除定時(shí)器,如果定時(shí)器的周期發(fā)生變化了,還要先清除定時(shí)器再重新按照新的周期來(lái)啟動(dòng)。這種最常用的場(chǎng)景就是九宮格抽獎(jiǎng),用戶(hù)點(diǎn)擊開(kāi)始抽獎(jiǎng)后,先緩慢啟動(dòng),然后逐漸變快,接口返回中獎(jiǎng)結(jié)果后,再開(kāi)始減速,最后停止。
我們很容易想到用 useEffect 來(lái)實(shí)現(xiàn)這樣的一個(gè) hook:
const useInterval = (callback, delay) => { useEffect(() => { if (delay !== null) { let id = setInterval(callback, delay); return () => clearInterval(id); } }, [delay]); };
我們把這段代碼用到項(xiàng)目中試試:
const Home = () => { const [count, setCount] = useState(0); useInterval(() => { console.log(count); setCount(count + 1); }, 500); return <div > { count } < /div>; };
可是這段運(yùn)行后很奇怪,頁(yè)面從 0 到 1 后,就再也不變了, console.log(count) 的輸出表明代碼并沒(méi)有卡死,那么問(wèn)題出在哪兒了?
React 組件中的 props 和 state 是可以改變的, React 會(huì)重渲染它們且「丟棄」任何關(guān)于上一次渲染的結(jié)果,它們之間不再有相關(guān)性。
useEffect() Hook 也「丟棄」上一次渲染結(jié)果,它會(huì)清除上一次 effect 再建立下一個(gè) effect,下一個(gè) effect 鎖住新的 props 和 state,這也是我們第一次嘗試簡(jiǎn)單示例可以正確工作的原因。
但 setInterval 不會(huì)「丟棄」。 它會(huì)一直引用老的 props 和 state 直到你把它換掉 —— 不重置時(shí)間你是無(wú)法做到的。
這里就要用到useRef這個(gè) hook 了,我們把 callback 存儲(chǔ)到 ref 中,當(dāng) callback 更新時(shí)去更新 ref.current
的值:
const useInterval = (callback, delay) => { const saveCallback = useRef(); useEffect(() => { // 每次渲染后,保存新的回調(diào)到我們的 ref 里 saveCallback.current = callback; }); useEffect(() => { function tick() { saveCallback.current(); } if (delay !== null) { let id = setInterval(tick, delay); return () => clearInterval(id); } }, [delay]); };
當(dāng)我們使用新的 useInterval 時(shí),發(fā)現(xiàn)就可以自增了
這里我們使用一個(gè)變量來(lái)控制增加的速度:
const [count, setCount] = useState(0); const [diff, setDiff] = useState(500); useInterval(() => { setCount(count + 1); }, diff); return ( <div> <p> count: {count} </p> <p> diff: {diff}ms </p> <p> <button onClick = {() => setDiff(diff - 50)}> 加快50ms </button> <button onClick = {() => setDiff(diff + 50)} > 減慢50ms </button> </p> </div>);
分別點(diǎn)擊兩個(gè)按鈕,可以調(diào)整count增加的速度。
3. 總結(jié)
使用react hook可以做很多有意思的事情,這里我們也僅僅是舉幾個(gè)簡(jiǎn)單的例子,后續(xù)我們也會(huì)更加深入了解hook的原理。
以上就是如何構(gòu)建自己的 react hooks的詳細(xì)內(nèi)容,更多關(guān)于如何構(gòu)建自己的 react hooks的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
React Native驗(yàn)證碼倒計(jì)時(shí)工具類(lèi)分享
這篇文章主要為大家分享了React Native驗(yàn)證碼倒計(jì)時(shí)工具類(lèi),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10React-Native之截圖組件react-native-view-shot的介紹與使用小結(jié)
這篇文章主要介紹了React-Native之截圖組件react-native-view-shot的介紹與使用小結(jié),需本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,要的朋友可以參考下2021-08-08webpack 2.x配置reactjs基本開(kāi)發(fā)環(huán)境詳解
本篇文章主要介紹了webpack 2.x配置reactjs基本開(kāi)發(fā)環(huán)境詳解,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08React循環(huán)遍歷渲染數(shù)組和對(duì)象元素方式
這篇文章主要介紹了React循環(huán)遍歷渲染數(shù)組和對(duì)象元素方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09淺談使用React.setState需要注意的三點(diǎn)
本篇文章主要介紹了淺談使用React.setState需要注意的三點(diǎn),提出了三點(diǎn)對(duì) React 新手來(lái)說(shuō)是很容易忽略的地方,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12