【问题标题】:Skip first useEffect render custom hook跳过第一个 useEffect 渲染自定义钩子
【发布时间】:2020-02-28 14:14:08
【问题描述】:

我想制作一个自定义钩子,它会跳过第一个 useEffect 渲染并在任何地方重用它。

我做了一个自定义钩子的工作示例:

function useEffectSkipFirst(fn, arr) {
  const isFirstRun = useRef(true);

  useEffect(() => {
    if (isFirstRun.current) {
      isFirstRun.current = false;
      return;
    }

    fn();
  }, [...arr]);
}

使用方法:

useEffectSkipFirst(fn, []);

问题是我收到了一些我试图理解的警告,希望你能帮助我:

React Hook useEffect 缺少依赖项:'fn'。要么包括它 或删除依赖数组。如果 'fn' 变化太频繁,找到 定义它的父组件并将该定义包装在 使用回调。 (react-hooks/exhaustive-deps) eslint

React Hook useEffect 在其依赖数组中有一个展开元素。 这意味着我们无法静态验证您是否通过了 正确的依赖关系。 (react-hooks/exhaustive-deps)

Codesandbox example.

完整示例代码:

import React, { useRef, useEffect, useState } from "react";
import ReactDOM from "react-dom";

function useEffectSkipFirst(fn, arr) {
  const isFirstRun = useRef(true);

  useEffect(() => {
    if (isFirstRun.current) {
      isFirstRun.current = false;
      return;
    }

    fn();
  }, [...arr]);
}

function App() {
  const [clicks, setClicks] = useState(0);
  const [date, setDate] = useState(Date.now());
  const [useEffectRunTimes, setTseEffectRunTimes] = useState(0);

  useEffectSkipFirst(() => {
    addEffectRunTimes();
  }, [clicks, date]);

  function addEffectRunTimes() {
    setTseEffectRunTimes(useEffectRunTimes + 1);
  }

  return (
    <div className="App">
      <div>clicks: {clicks}</div>
      <div>date: {new Date(date).toString()}</div>
      <div>useEffectRunTimes: {useEffectRunTimes}</div>
      <button onClick={() => setClicks(clicks + 1)}>add clicks</button>
      <button onClick={() => setDate(Date.now())}>update date</button>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案1】:

    您作为对useEffectSkipFirst 的回调的 fn 函数在每次渲染时都会发生变化。此外,由于在 useEffectSkipFirst 中调用了此函数,因此 linter 会提示您将其定义为依赖项。

    但是,您实现它的方式取决于实现useEffectSkipFirst 的代码,以确保所有值都具有正确的闭包,您可以跳过将其定义为useEffect 的依赖项。如果你这样做,它可能会导致无限循环。

    您也可以查看此帖子了解更多详情:How to fix missing dependency warning when using useEffect React Hook?

    【讨论】:

    • 那么有没有办法制作可重用的自定义钩子? :(
    • 它是一个可重用的钩子,但是你不必担心函数依赖,因为任何依赖都会被使用它的人作为自定义钩子的第二个参数传递
    【解决方案2】:

    第一个警告基本上意味着useEffect 的回调中引用的所有依赖项都必须在其依赖项数组中声明。 The official documentation 提供了更多关于向依赖数组添加函数的说明。

    第二个警告意味着您不能动态指定依赖项,即以一种直到运行时才知道它们的方式。这是因为依赖关系在代码执行之前是静态比较的,因此必须明确说明。

    【讨论】:

      【解决方案3】:

      我看到了您的问题的一种解决方案。

      首先,您需要将 useEffect 依赖项数组减少为单个值(通过使用挂钩 useMemo)。

      那么在效果中,你应该使用useRef来存储这个依赖值的旧值,这样你就可以只有在依赖发生变化时才能调用fn函数。

      这是自定义钩子的代码:

      function useEffectSkipFirst(fn, depValue) {
        const oldDepValueRef = useRef(depValue);
      
        useEffect(() => {
          if (depValue !== oldDepValueRef.current) {
            // Note that this code will not be executed the first time as
            // we initialized oldDepValueRef with depValue
            fn();
            oldDepValueRef.current = depValue;
          }
        }, [fn, depValue]);
      }
      

      这是使用useMemo 将数组dep 减少为单个值的代码:

      // I decided the return an object but it could be an array or something else
      const depValue = useMemo(() => ({}), [clicks, date]);
      
      useEffectSkipFirst(addEffectRunTimes, depValue);
      
      function addEffectRunTimes() {
        // When the value depends on the previous value, 
        // you can use a function to update the state value
        setTseEffectRunTimes(v => v + 1);
      }
      

      顺便说一句,如果addEffectRunTimes函数只用在效果中,你可以直接写:

      useEffectSkipFirst(() => setTseEffectRunTimes(v => v + 1), depValue);
      

      【讨论】:

        猜你喜欢
        • 2020-01-11
        • 2022-01-10
        • 2022-09-23
        • 2021-11-02
        • 2022-07-18
        • 2021-01-06
        • 2019-12-05
        • 2021-11-22
        • 2022-10-09
        相关资源
        最近更新 更多