【问题标题】:Determine which dependency array variable caused useEffect hook to fire确定哪个依赖数组变量导致 useEffect 钩子触发
【发布时间】:2019-08-06 19:49:39
【问题描述】:

有没有一种简单的方法可以确定useEffect 的依赖数组中的哪个变量会触发函数重新触发?

简单地注销每个变量可能会产生误导,如果a 是一个函数而b 是一个对象,它们在记录时可能看起来相同,但实际上却不同并导致 useEffect 触发。

例如:

React.useEffect(() => {
  // which variable triggered this re-fire?
  console.log('---useEffect---')
}, [a, b, c, d])

我当前的方法一直在逐个删除依赖变量​​,直到我注意到导致过多 useEffect 调用的行为,但必须有更好的方法来缩小范围。

【问题讨论】:

  • 只是想一想,如果您需要验证哪个变量发生了变化,那么拥有多个useEffects (每个变化的变量一个可能独立变化的变量)不是很有意义。因为很明显您正在尝试将两个用例合二为一?

标签: javascript reactjs react-hooks


【解决方案1】:

更新

经过一点实际的使用,我到目前为止喜欢以下解决方案,它借鉴了 Retsam 解决方案的某些方面:

const compareInputs = (inputKeys, oldInputs, newInputs) => {
  inputKeys.forEach(key => {
    const oldInput = oldInputs[key];
    const newInput = newInputs[key];
    if (oldInput !== newInput) {
      console.log("change detected", key, "old:", oldInput, "new:", newInput);
    }
  });
};
const useDependenciesDebugger = inputs => {
  const oldInputsRef = useRef(inputs);
  const inputValuesArray = Object.values(inputs);
  const inputKeysArray = Object.keys(inputs);
  useMemo(() => {
    const oldInputs = oldInputsRef.current;

    compareInputs(inputKeysArray, oldInputs, inputs);

    oldInputsRef.current = inputs;
  }, inputValuesArray); // eslint-disable-line react-hooks/exhaustive-deps
};

然后可以通过复制依赖数组文字并将其更改为对象文字来使用它:

useDependenciesDebugger({ state1, state2 });

这允许日志记录知道变量的名称,而无需为此目的使用任何单独的参数。

【讨论】:

  • 我也喜欢这个答案。与我的回答相比,设置起来需要,但会提供更好的输出,因为每个依赖项都有一个名称,而我的只是说明哪个索引发生了变化。
  • 如果您想在旧值更改时记录旧值以及新值,您可以从持有truefalse 的引用切换到持有null{prevValue: value} 的引用。
【解决方案2】:

据我所知,开箱即用并没有真正简单的方法,但您可以放入一个自定义挂钩来跟踪其依赖项并记录更改的日志:

// Same arguments as useEffect, but with an optional string for logging purposes
const useEffectDebugger = (func, inputs, prefix = "useEffect") => {
  // Using a ref to hold the inputs from the previous run (or same run for initial run
  const oldInputsRef = useRef(inputs);
  useEffect(() => {
    // Get the old inputs
    const oldInputs = oldInputsRef.current;

    // Compare the old inputs to the current inputs
    compareInputs(oldInputs, inputs, prefix)

    // Save the current inputs
    oldInputsRef.current = inputs;

    // Execute wrapped effect
    func()
  }, inputs);
};

compareInputs 位可能如下所示:

const compareInputs = (oldInputs, newInputs, prefix) => {
  // Edge-case: different array lengths
  if(oldInputs.length !== newInputs.length) {
    // Not helpful to compare item by item, so just output the whole array
    console.log(`${prefix} - Inputs have a different length`, oldInputs, newInputs)
    console.log("Old inputs:", oldInputs)
    console.log("New inputs:", newInputs)
    return;
  }

  // Compare individual items
  oldInputs.forEach((oldInput, index) => {
    const newInput = newInputs[index];
    if(oldInput !== newInput) {
      console.log(`${prefix} - The input changed in position ${index}`);
      console.log("Old value:", oldInput)
      console.log("New value:", newInput)
    }
  })
}

你可以这样使用:

useEffectDebugger(() => {
  // which variable triggered this re-fire?
  console.log('---useEffect---')
}, [a, b, c, d], 'Effect Name')

你会得到如下输出:

Effect Name - The input changed in position 2
Old value: "Previous value"
New value: "New value"

【讨论】:

    【解决方案3】:

    还有另一个堆栈溢出线程说明您可以使用 useRef 来查看以前的值。

    https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state

    【讨论】:

    • 发布指向其他 StackOverflow 线程的链接可能很有用。
    【解决方案4】:

    我最终从各种答案中汲取了一点点来制作我自己的钩子。我希望能够只删除一些东西来代替 useEffect 以快速调试触发 useEffect 的依赖项。

    const usePrevious = (value, initialValue) => {
      const ref = useRef(initialValue);
      useEffect(() => {
        ref.current = value;
      });
      return ref.current;
    };
    
    const useEffectDebugger = (effectHook, dependencies, dependencyNames = []) => {
      const previousDeps = usePrevious(dependencies, []);
    
      const changedDeps = dependencies.reduce((accum, dependency, index) => {
        if (dependency !== previousDeps[index]) {
          const keyName = dependencyNames[index] || index;
          return {
            ...accum,
            [keyName]: {
              before: previousDeps[index],
              after: dependency
            }
          };
        }
    
        return accum;
      }, {});
    
      if (Object.keys(changedDeps).length) {
        console.log('[use-effect-debugger] ', changedDeps);
      }
    
      useEffect(effectHook, dependencies);
    };
    

    以下是两个示例。对于每个示例,我假设dep2 从“foo”变为“bar”。示例 1 显示了 没有 传递 dependencyNames 的输出,示例 2 显示了一个示例 dependencyNames

    示例 1

    之前:

    useEffect(() => {
      // useEffect code here... 
    }, [dep1, dep2])
    

    之后:

    useEffectDebugger(() => {
      // useEffect code here... 
    }, [dep1, dep2])
    

    控制台输出:

    {
      1: {
        before: 'foo',
        after: 'bar'
      }
    }
    

    对象键“1”表示已更改依赖项的索引。这里,dep1 发生了变化,是依赖项中的第二项,或索引 1

    示例 2

    之前:

    useEffect(() => {
      // useEffect code here... 
    }, [dep1, dep2])
    

    之后:

    useEffectDebugger(() => {
      // useEffect code here... 
    }, [dep1, dep2], ['dep1', 'dep2'])
    

    控制台输出:

    {
      dep2: {
        before: 'foo',
        after: 'bar'
      }
    }
    

    【讨论】:

    • 你应该把它发布到 NPM!
    • 这太棒了。
    • 在“这里,dep1 发生了变化,是依赖项中的第二项,或索引 1”处有一个小而重要的错字 - 它应该是 dep2我>!
    【解决方案5】:

    这个库... @simbathesailor/use-what-changed ,就像一个魅力!

    1. Installnpm/yarn--dev--no-save
    2. 添加导入:
    import { useWhatChanged } from '@simbathesailor/use-what-changed';
    
    1. 叫它:
    // (guarantee useEffect deps are in sync with useWhatChanged)
    let deps = [a, b, c, d]
    
    useWhatChanged(deps, 'a, b, c, d');
    useEffect(() => {
      // your effect
    }, deps);
    

    在控制台中创建这个漂亮的图表:

    有两个常见的罪魁祸首:

    1. 一些对象像这样传入:
    // Being used like:
    export function App() {
      return <MyComponent fetchOptions={{
        urlThing: '/foo',
        headerThing: 'FOO-BAR'
      })
    }
    export const MyComponent = ({fetchOptions}) => {
      const [someData, setSomeData] = useState()
      useEffect(() => {
        window.fetch(fetchOptions).then((data) => {
          setSomeData(data)
        })
    
      }, [fetchOptions])
    
      return <div>hello {someData.firstName}</div>
    }
    

    在对象情况下的修复,如果可以的话,在组件渲染之外打破一个静态对象:

    const fetchSomeDataOptions = {
      urlThing: '/foo',
      headerThing: 'FOO-BAR'
    }
    export function App() {
      return <MyComponent fetchOptions={fetchSomeDataOptions} />
    }
    

    你也可以用 useMemo 换行:

    export function App() {
      return <MyComponent fetchOptions={
        useMemo(
          () => {
            return {
              urlThing: '/foo',
              headerThing: 'FOO-BAR',
              variableThing: hash(someTimestamp)
            }
          },
          [hash, someTimestamp]
        )
      } />
    }
    

    同样的概念在一定程度上也适用于函数,但最终可能会出现陈旧的闭包。

    【讨论】:

    • (点表示值没有改变。绿色对号表示它确实改变了。)甚至还有一个 babel 插件(认真地去明星这个家伙项目!)github.com/simbathesailor/use-what-changed
    • 知道为什么,但它没有为我记录任何内容
    • @JamilAlisgenderov 我认为 useWhatChanged 必须使用 console.table.. 因此,如果您尝试在不支持 console.table 的旧浏览器中进行测试,您可以检查是否定义了 console.table。您还可以验证正常的 console.log('something changed', 'table defined?', !!console.table);在你的 useEffect 挂钩日志中。否则......也许用你的反应版本+浏览器在github上提出问题
    • @JamilAlisgenderov 有没有想过是什么导致 use-what-c​​hanged 没有为您记录任何内容?
    • 故事书似乎不支持它
    猜你喜欢
    • 2020-01-23
    • 1970-01-01
    • 2020-02-26
    • 2020-10-20
    • 2020-06-03
    • 2021-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多