一个监听变量变化的语法糖

由来

在实践中, 我们(我)经常会遇到, 当某变量变化的时候, 执行其他方法(副作用), 通常我们使用 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)
      })
    

如非特别声明,本站作品均为原创,遵循【自由转载-保持署名-非商用-非衍生 创意共享 3.0 许可证】。

对于转载作品,如需二次转载,请遵循原作许可。