当依赖项是一个函数时,如何避免 useEffect 中的无限更新

Mar*_*rco 3 typescript reactjs react-hooks

当页面呈现时,挂钩会检查存储中是否存在持久的模拟信息,如果有,则在全局 AppState 上下文中设置这些信息。

const impersonateStorageKey = `impersonation_${config.environment}`
// signature const impersonate: (empScope: number) => void
const { currentImpersonation, impersonate } = useAppContext()

useEffect(() => {
    if (!window || !window.localStorage) return;

    const storageString = localStorage.getItem(impersonateStorageKey)
    if (!storageString) return;

    const data = JSON.parse(storageString)
    impersonate(data.currentImpersonation)
}, [impersonateStorageKey])
Run Code Online (Sandbox Code Playgroud)

使用第二个钩子,将当前模拟身份的更改持久保存到存储中:

useEffect(() => {
    if (!window || !window.localStorage) return;
    localStorage.setItem(impersonateStorageKey, JSON.stringify({ /* ... */}))
}, [currentImpersonation, impersonateStorageKey])
Run Code Online (Sandbox Code Playgroud)

加上相关位useAppContext

const useAppContext = () => {
    const { state, dispatch } = useContext(AppContext)
    if (!state) {
        throw new Error("useAppContext must be used within within  AppContextProvider")
    }
  
    const impersonate = (employeeScope: string | number) => dispatch({ type: 'IMPERSONATE', value: employeeScope })
    const currentImpersonation = state.currentImpersonation

    return {
        impersonate,
        currentImpersonation,
    }
}
Run Code Online (Sandbox Code Playgroud)

这是正常工作的,但是 linter 抱怨第一个 useEffect 钩子中缺少依赖模拟

当我添加impersonate到依赖项数组时,这将导致不断的更新循环并使应用程序无响应。

我知道是什么导致了这种行为,但我没有找到关于如何打破循环并使linter 满意的解决方案(除了忽略规则)。

我在这里可以采取什么方法?

Ori*_*ori 5

您可以在创建函数时使用以下命令记住该函数useCallback

const useAppContext = () => {
  const { state, dispatch } = useContext(AppContext)

  const impersonate = useCallback(
    (employeeScope: string | number) => dispatch({
      type: 'IMPERSONATE',
      value: employeeScope
    }), [dispatch])

  if (!state) {
    throw new Error("useAppContext must be used within within  AppContextProvider")
  }

  const currentImpersonation = state.currentImpersonation

  return {
    impersonate,
    currentImpersonation,
  }
}
Run Code Online (Sandbox Code Playgroud)

如果不能,解决方法是将函数放入ref中。是ref对对象的不可变引用,其current属性是可变的。由于它ref本身是不可变的,因此您可以将其用作依赖项,而不会导致useEffect激活(linter 知道它,您甚至不需要将其声明为依赖项)。你可以current随心所欲地变异。

const impersonateRef = useRef(impersonate)

useEffect(() => {
  impersonateRef.current = impersonate
}, [impersonate])

useEffect(() => {
  if (!window || !window.localStorage) return

  const storageString = localStorage.getItem(impersonateStorageKey)
  if (!storageString) return

  const data = JSON.parse(storageString)
  impersonateRef.current(data.currentImpersonation)
}, [impersonateStorageKey])
Run Code Online (Sandbox Code Playgroud)

  • @OriDrori 将 `useCallback` 用法移到 `if` 条件之前。不能有条件地调用 Hooks。 (2认同)