一个监听变量变化的语法糖
由来
在实践中, 我们(我)经常会遇到, 当某变量变化的时候, 执行其他方法(副作用), 通常我们使用 useEffect
:
function useHook() {
const [varA] = useState();
useEffect(() => {
// do something
}, [varA]);
}
但是如果执行的方法中包含其他响应式变量 varB
, 我们需要 varB
的最新值, 但却不希望 varB
变化时触发 useEffect
重新执行, 那我们可能需要 hack:
function useHook() {
const [varA] = useState();
const [varB] = useState();
// mui 的 useEventCallback
const effect = useEventCallback(() => {
doSomething(varB);
});
useEffect(() => {
effect();
}, [varA, effect]);
}
实现
因此我们可以写一个小小的语法糖来简化这一目的:
import { useEventCallback } from '@mui/material'
import useEnhancedEffect from '@mui/material/utils/useEnhancedEffect'
import { useRef } from 'react'
/**
* @example
* ``` tsx
* function Component() {
* const [count, setCount] = useState(0)
*
* // will be triggered when count changed
* useListen(count, (next, prev) => {
* console.log(prev, next)
* })
* }
* ```
*/
export function useListen<T>(
value: T,
callback: (next: T, prev: T | undefined) => void
) {
const isFirstCallbackRef = useRef(true)
const prevRef = useRef<T | undefined>(undefined)
const callbackRef = useEventCallback(callback)
useEnhancedEffect(() => {
// useEffect 在 dev 环境会执行 2 遍, 此处避免该行为造成的影响
if (value === prevRef.current && !isFirstCallbackRef.current) {
return
}
isFirstCallbackRef.current = false
callbackRef(value, prevRef.current)
prevRef.current = value
}, [value, callbackRef])
}
用法
function Test() {
const [varA, setVarA] = useState()
useListen(varA, (next, prev) => {
console.log({ next, prev })
})
// ...
}
利弊分析
优势
-
语义明晰
- 就是为了监听变量变化而生
-
免除各种冗余的格式代码
不足
- 不应用于添加事件监听等
useListen(varA, () => { // 错误用法, 因为没有消除副作用 (removeEventListener) window.addEventListener('event-name', callback) })