Skip to content

Hooks · usePrevious & useCompareEffect

上一篇在写 useRequest 的时候,其中有些可以复用的 hooks,我们这一篇把相关的 hooks拿出来,简洁描述下,具体 hooks 的名字不用太关注,我们主要关注其使用的场景。

usePrevious

hook主要是用来记忆上一次的值的,一般使用场景为需要有对比当前值以及上一次的值的时候。具体实现比较容易理解,代码如下:

js
import { useEffect, useRef } from 'react'

// 版本1
export default function usePrevious(state) {
  const previousRef = useRef()
  const currentRef = useRef()

  useEffect(() => {
    previousRef.current = currentRef.current
    currentRef.current = state
  }, [state])

  return previousRef.current
}

// 版本2
export function usePrevious2(state) {
  const previousRef = useRef()
  const currentRef = useRef()

  previousRef.current = currentRef.current
  currentRef.current = state

  return previousRef.current
}

// 版本3
export function usePrevious3(state) {
  const previousRef = useRef()
  const currentRef = useRef()

  if (!Object.is(currentRef.current, state)) {
    previousRef.current = currentRef.current
    currentRef.current = state
  }

  return previousRef.current
}

使用 2 个 Ref 记录当前 state 和上一个 state 值,如果当前 state 监听到有改动,则进行重新赋值,最终返回的是上一个 state 值。其中赋值这里逻辑为:先把 当前 state 赋值给 上一个 state. 再把最新 state 赋值给 当前 state

useCompareEffect

hook 主要是二次封装 useEffect 对其 deps 进行值的比较,如果值不同才会执行对应回调函数。使用场景可以对比 useEffect, 例如:

jsx
const [params, setParams] = useState({ search: '' })

useCompareEffect(() => {
  fetch('xx')
}, [params])

以上例子,当 params.search 值改变,才会进行请求。功能分析的话,该 hook 需要实现如下功能

  • 保存上一次的 state
  • 深度对比 2 个state的值是否相等

第一点使用上面的 usePrevious 便可, 第二点也有很多实现方式,我们先假设已有此功能函数。 先关注核心 useCompareEffect的实现逻辑。

js
import { isEqual } from '@slsanyi/utils'

// 方式一
export function useCompareEffect1(fn, deps) {
  const preValue = usePrevious(deps)

  useEffect(() => {
    if (isEqual(preValue, deps)) return

    return fn()
  }, deps)
}

// 方式二
export function useCompareEffect2(fn, deps) {
  const preValue = usePrevious(deps)
  const signalRef = useRef(0)

  if (!isEqual(preValue, deps)) {
    signalRef.current += 1
  }

  useEffect(fn, [signalRef.current])
}

其中以上代码中,方式一 会在React.StrictMode模式下,出现异常,原因在于严格模式下,App 组件执行了 2 次,其中 useRequest 也运行 2 次,useCompareEffect 也运行 2 次,usePrevious 也运行 2 次。

js
// usePrevious

// 1 => pre = 0,cur = 1.
// 1 => pre = 1, cur = 1

假设 usePrevious(1) 执行 2 次, 第一次 最终值为 cur = 1, pre = 0, 第二次再执行就变成了 cur = 1, pre = 1. 最终造成了异步执行的回掉函数函数 useEffect 第一个参数在判断是否 相等的时候,因为 上一次和当前的 state 相同,所以没有执行到最终的函数.

方式二通过判断不同的情形,再执行 useEffect,从而保证只有不等的时候执行一次副作用回掉函数函数。