Определите, какая переменная массива зависимостей вызвала ловушку useEffect

Есть ли простой способ определить, какая переменная в массиве зависимостей useEffect вызывает перезапуск функции?

Простой выход из каждой переменной может ввести в заблуждение, если a - это функция, а b - объект, они могут выглядеть одинаково при регистрации, но на самом деле они разные и вызывают срабатывание useEffect.

Например:

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

Мой текущий метод удалял переменные зависимости одну за другой, пока я не заметил поведение, которое вызывает чрезмерные вызовы useEffect, но должен быть лучший способ сузить это.

Ответ 1

Насколько я знаю, нет действительно простого способа сделать это из коробки, но вы могли бы добавить пользовательский хук, который отслеживает его зависимости и регистрирует, какой из них изменился:

// 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"

Ответ 2

UPDATE

После небольшого использования в реальном мире мне пока нравится следующее решение, которое заимствует некоторые аспекты решения 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 });

Это позволяет журналу знать имена переменных без какого-либо отдельного параметра для этой цели.

Edit useDependenciesDebugger