TypeScript從零實現React自定義Hook,實現Vue中的watch功能。
- 2020 年 4 月 11 日
- 筆記
前言
在Vue中,我們經常需要用watch去觀察一個值的變化,通過新舊值的對比去做一些事情。
但是React Hook中好像並沒有提供類似的hook來讓我們實現相同的事情
不過好在Hook的好處就在於它可以自由組合各種基礎Hook從而實現強大的自定義Hook。
本篇文章就帶你打造一個簡單好用的use-watch hooks。
實現
實現雛形
首先分析一下Vue中watch的功能,就是一個響應式的值發生改變以後,會觸發一個回調函數,那麼在React中自然而然的就想到了useEffect這個hook,我們先來打造一個基礎的代碼雛形,把我們想要觀察的值作為useEffect的依賴傳入。
type Callback<T> = (prev: T | undefined) => void; function useWatch<T>(dep: T, callback: Callback<T>) { useEffect(() => { callback(); }, [dep]); } 複製代碼
現在我們使用的時候就可以
const App: React.FC = () => { const [count, setCount] = useState(0); useWatch(count, () => { console.log('currentCount: ', count); }) const add = () => setCount(prevCount => prevCount + 1) return ( <div> <p> 當前的count是{count}</p> {count} <button onClick={add} className="btn">+</button> </div> ) } 複製代碼
實現oldValue
在每次count發生變化的時候,會執行傳入的回調函數。
現在我們加入舊值的保存邏輯,以便於在每次調用傳進去的回調函數的時候,可以在回調函數中拿到count上一次的值。
什麼東西可以在一個組件的生命周期中充當一個存儲器的功能呢,當然是useRef
啦。
function useWatch<T>(dep: T, callback: Callback<T>) { const prev = useRef<T>(); useEffect(() => { callback(prev.current); prev.current = dep; }, [dep]); return () => { stop.current = true; }; } 複製代碼
這樣就在每一次更新prev里保存的值為最新的值之前,先調用callback函數把上一次保留的值給到外部。
現在外部使用的時候 就可以
const App: React.FC = () => { const [count, setCount] = useState(0); useWatch(count, (oldCount) => { console.log('oldCount: ', oldCount); console.log('currentCount: ', count); }) const add = () => setCount(prevCount => prevCount + 1) return ( <div> <p> 當前的count是{count}</p> {count} <button onClick={add} className="btn">+</button> </div> ) } 複製代碼
實現immediate
其實到此為止,已經實現了Vue中watch的主要功能了,
現在還有一個問題是useEffect
會在組件初始化的時候就默認調用一次,而watch的默認行為不應該這樣。
現在需要在組件初始化的時候不要調用這個callback,還是利用useRef
來做,利用一個標誌位inited來保存組件是否初始化的標記。
並且通過第三個參數config來允許用戶改變這個默認行為。
type Callback<T> = (prev: T | undefined) => void; type Config = { immediate: boolean; }; function useWatch<T>(dep: T, callback: Callback<T>, config: Config = { immediate: false }) { const { immediate } = config; const prev = useRef<T>(); const inited = useRef(false); useEffect(() => { const execute = () => callback(prev.current); if (!inited.current) { inited.current = true; if (immediate) { execute(); } } else { execute(); } prev.current = dep; }, [dep]); } 複製代碼
實現stop
還是通過useRef
做,只是把控制ref標誌的邏輯暴露給外部。
type Callback<T> = (prev: T | undefined) => void; type Config = { immediate: boolean; }; function useWatch<T>(dep: T, callback: Callback<T>, config: Config = { immediate: false }) { const { immediate } = config; const prev = useRef<T>(); const inited = useRef(false); const stop = useRef(false); useEffect(() => { const execute = () => callback(prev.current); if (!stop.current) { if (!inited.current) { inited.current = true; if (immediate) { execute(); } } else { execute(); } prev.current = dep; } }, [dep]); return () => { stop.current = true; }; } 複製代碼
這樣在外部就可以這樣去停止本次觀察。
const App: React.FC = () => { const [prev, setPrev] = useState() const [count, setCount] = useState(0); const stop = useWatch(count, (prevCount) => { console.log('prevCount: ', prevCount); console.log('currentCount: ', count); setPrev(prevCount) }) const add = () => setCount(prevCount => prevCount + 1) return ( <div> <p> 當前的count是{count}</p> <p> 前一次的count是{prev}</p> {count} <button onClick={add} className="btn">+</button> <button onClick={stop} className="btn">停止觀察舊值</button> </div> ) } 複製代碼
源碼地址:
文檔地址:
文檔是基於docz生成的,配合mdx還可以實現非常好用的功能預覽: sl1673495.github.io/use-watch-h…