在Vue中,咱們常常須要用watch去觀察一個值的變化,經過新舊值的對比去作一些事情。git
可是React Hook中好像並無提供相似的hook來讓咱們實現相同的事情github
不過好在Hook的好處就在於它能夠自由組合各類基礎Hook從而實現強大的自定義Hook。函數
本篇文章就帶你打造一個簡單好用的use-watch hooks。ui
首先分析一下Vue中watch的功能,就是一個響應式的值發生改變之後,會觸發一個回調函數,那麼在React中天然而然的就想到了useEffect這個hook,咱們先來打造一個基礎的代碼雛形,把咱們想要觀察的值做爲useEffect的依賴傳入。spa
type Callback<T> = (prev: T | undefined) => void;
function useWatch<T>(dep: T, callback: Callback<T>) {
useEffect(() => {
callback();
}, [dep]);
}
複製代碼
如今咱們使用的時候就能夠code
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>
)
}
複製代碼
在每次count發生變化的時候,會執行傳入的回調函數。xml
如今咱們加入舊值的保存邏輯,以便於在每次調用傳進去的回調函數的時候,能夠在回調函數中拿到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函數把上一次保留的值給到外部。get
如今外部使用的時候 就能夠
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>
)
}
複製代碼
其實到此爲止,已經實現了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]);
}
複製代碼
仍是經過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…