當我們想在兩個函數(shù)之間共享邏輯時吠昭,我們會把它提取到第三個函數(shù)中喊括。而組件和Hook都是函數(shù)矢棚,所以也同樣適用這種方式。
認識自定義Hook
自定義Hook是一個函數(shù)蒲肋,其名稱以use
開頭,函數(shù)內部可以調用其他Hook兜粘。
在自定義Hook的頂層可以無條件地調用其他Hook(useState
, useEffect
)申窘。
我們可以自由決定自定義Hook的參數(shù)和返回值孔轴。
自定義Hook必須以use開頭,這樣方便判斷該函數(shù)內部是否調用了內部Hook路鹰,以及React能自動檢查Hook是否違反了Hook的規(guī)則(見Hook規(guī)則部分)。
每次使用自定義Hook時悍引,其中的state和副作用都是完全隔離的恩脂。
Hooks 和普通函數(shù)在語義上是有區(qū)別的趣斤,就在于函數(shù)中有沒有用到其它 Hooks。
就是說如果你創(chuàng)建了一個 useXXX 的函數(shù)浓领,但是內部并沒有用任何其它 Hooks,那么這個函數(shù)就不是一個 Hook联贩,而只是一個普通的函數(shù)漫仆。但是如果用了其它 Hooks 泪幌,那么它就是一個 Hook构韵。
自定義Hook的特點
- 名字一定是以
use
開頭的函數(shù)曹抬,這樣 React 才能夠知道這個函數(shù)是一個 Hook蜗元。 - 函數(shù)內部一定調用了其它的 Hooks,可以是內置的 Hooks届宠,也可以是其它自定義 Hooks嘹锁。這樣才能夠讓組件刷新,或者去產(chǎn)生副作用赶熟。
可重用邏輯直接寫一個工具類不就行了嗎?為什么一定要通過Hook進行封裝呢映砖?
因為在 Hooks 中,你可以管理當前組件的
state
罩旋,從而將更多的邏輯寫在可重用的 Hooks 中眶诈。但是要知道涨醋,在普通的工具類中是無法直接修改組件state
的逝撬,那么也就無法在數(shù)據(jù)改變的時候觸發(fā)組件的重新渲染。
拆分邏輯的目的不一定是為了重用宪潮,而可以是僅僅為了業(yè)務邏輯的隔離。
在這個場景下狡相,我們不一定要把 Hooks 放到獨立的文件中梯轻,而是可以和函數(shù)組件寫在一個文件中尽棕。這么做的原因就在于,這些 Hooks 是和當前函數(shù)組件緊密相關的滔悉,所以寫到一起,反而更容易閱讀和理解回官。
例:
function MyComponent() {
const [id, setId] = useState(1);
const isOnline = useOnlineStatus(id);
return (
<>
// other nodes
</>
)
}
useState
為我們提供了id
的最新值曹宴,并把它做為參數(shù)傳入useOnlineStatus
, 當id
改變時歉提,useOnlineStatus
Hook會取消訂閱前一個id
,并訂閱新的id
苔巨。
例一:自定義 Hook 處理 LocalStorage 的存取
需求:希望把一些數(shù)據(jù)存儲到
localStorage
中 - 不使用自定義Hook
不使用自定義Hook
import React, { useState, useEffect } from 'react'
export default function CustomDataStoreHook() {
const [name, setName] = useState(() => {
return JSON.parse(window.localStorage.getItem("name"))
});
useEffect(() => {
window.localStorage.setItem("name", JSON.stringify(name));
}, [name])
return (
<div>
<h2>CustomDataStoreHook: {name}</h2>
<button onClick={e => setName("gercke")}>設置name</button>
</div>
)
}
定義自定義Hook - useLocalStorage
import React,{useState, useEffect} from 'react';
function useLocalStorage(key) {
const [data, setData] = useState(() => {
return JSON.parse(window.localStorage.getItem(key))
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(data));
}, [data]);
return [data, setData];
}
export default useLocalStorage;
使用自定義Hook
import React, { useState, useEffect } from 'react';
import useLocalStorage from '../hooks/local-store-hook';
export default function CustomDataStoreHook() {
const [name, setName] = useLocalStorage("name");
return (
<div>
<h2>CustomDataStoreHook: {name}</h2>
<button onClick={e => setName("kobe")}>設置name</button>
</div>
)
}
例二:自定義Hook監(jiān)聽瀏覽器狀態(tài)變化
需求:當
y > 300
時,顯示Back to top
按鈕
定義自定義Hook - useScroll
import { useState, useEffect } from 'react';
// 獲取橫向恋拷,縱向滾動條位置
const getPosition = () => {
return {
x: document.body.scrollLeft,
y: document.body.scrollTop,
};
};
const useScroll = () => {
// 定一個 position 這個 state 保存滾動條位置
const [position, setPosition] = useState(getPosition());
useEffect(() => {
const handler = () => {
setPosition(getPosition(document));
};
// 監(jiān)聽 scroll 事件,更新滾動條位置
document.addEventListener("scroll", handler);
return () => {
// 組件銷毀時蔬顾,取消事件監(jiān)聽
document.removeEventListener("scroll", handler);
};
}, []);
return position;
};
使用自定義Hook
import React, { useCallback } from 'react';
import useScroll from './useScroll';
function ScrollTop() {
const { y } = useScroll();
const goTop = useCallback(() => {
document.body.scrollTop = 0;
}, []);
const style = {
position: "fixed",
right: "10px",
bottom: "10px",
};
// 當滾動條位置縱向超過 300 時湘捎,顯示返回頂部按鈕
if (y > 300) {
return (
<button onClick={goTop} style={style}>
Back to Top
</button>
);
}
// 否則不 render 任何 UI
return null;
}
Hook 規(guī)則
- 只在最頂層使用Hook
- 不要在循環(huán)、條件或嵌套中調用Hook
- 只在React函數(shù)中調用Hook
- 在react的函數(shù)組件中調用Hook
- 在自定義Hook中調用其他Hook