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

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

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

Например:

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

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

Просто мысль, если вам нужно проверить, какая переменная изменилась, не имеет ли смысл иметь несколько useEffects (по одному для каждой изменяющейся переменной, которая может меняться независимо). Потому что ясно, что вы пытаетесь соединить два варианта использования в один?

Archmede 26.11.2021 19:55
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
114
1
27 040
5

Ответы 5

ОБНОВИТЬ

После небольшого реального использования мне пока нравится следующее решение, которое заимствует некоторые аспекты решения Retsam:

const compareInputs = (inputKeys, oldInputs, newInputs) => {
  inputKeys.forEach(key => {
    const oldInput = oldInputs[key];
    const newInput = newInputs[key];
    if (oldInput !== newInput) {
      console.info("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

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

Retsam 15.03.2019 20:25

Вы можете переключиться с ссылки, содержащей true и false, на одну, содержащую null и {prevValue: value}, если вы хотите регистрировать старое значение, а также новое значение при его изменении.

Retsam 15.03.2019 20:27

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

// 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.info(`${prefix} - Inputs have a different length`, oldInputs, newInputs)
    console.info("Old inputs:", oldInputs)
    console.info("New inputs:", newInputs)
    return;
  }

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

Вы можете использовать это следующим образом:

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

И вы получите вывод, например:

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

Есть еще один поток переполнения стека, в котором говорится, что вы можете использовать useRef, чтобы увидеть предыдущее значение.

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

Может быть полезно опубликовать ссылку на другую ветку StackOverflow.

jknotek 01.10.2019 01:47

В итоге я взял немного из разных ответов, чтобы сделать для этого свой собственный крючок. Я хотел иметь возможность просто добавить что-то вместо 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.info('[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!

goldylucks 29.08.2020 17:18

Это круто.

Ryan 31.08.2020 17:04

В «Здесь отп1 изменен и является вторым элементом в зависимости или индексом 1» есть небольшая, но важная опечатка — здесь должно быть написано dep2!

Felipe Maia 29.12.2021 10:36

это каким-то образом должно быть поведением по умолчанию.

Pinwheeler 05.01.2022 03:19

Эта библиотека... @simbathesailor/use-what-changed работает как часы!

  1. Install с npm/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);

Создает эту красивую диаграмму в консоли:

image loaded from github

Есть два распространенных виновника:

  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/simbatesailor/use-what-changed

Devin Rhode 10.09.2020 23:13

не знаю почему, но он ничего не регистрирует для меня

Jamil Alisgenderov 19.10.2020 13:51

@JamilAlisgenderov Я думаю, что useWhatChanged должен использовать console.table .. поэтому, если вы пытаетесь протестировать в более старом браузере, который не поддерживает console.table, вы можете проверить, определено ли console.table. Вы также можете проверить нормальный console.info('что-то изменилось', 'таблица определена?', !!console.table); внутри ваших журналов ловушек useEffect. В противном случае... возможно, создайте проблему на github с вашей версией реакции + браузером.

Devin Rhode 19.10.2020 16:27

@JamilAlisgenderov Когда-нибудь выясняли, что было причиной того, что use-what-changed ничего не регистрировал для вас?

Devin Rhode 09.12.2020 22:07

кажется, это не поддерживается в сборнике рассказов

Vinujan.S 29.09.2021 16:21

Другие вопросы по теме