在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…