Есть ли простой способ определить, какая переменная в массиве зависимостей useEffect вызывает повторное срабатывание функции?
Простое отключение каждой переменной может ввести в заблуждение, если a — это функция, а b — объект, они могут выглядеть одинаково при регистрации, но на самом деле будут разными и вызывать срабатывание useEffect.
Например:
React.useEffect(() => {
// which variable triggered this re-fire?
console.info('---useEffect---')
}, [a, b, c, d])
Мой текущий метод удаляет переменные зависимости одну за другой, пока я не замечу поведение, которое вызывает чрезмерные вызовы useEffect, но должен быть лучший способ сузить это.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


ОБНОВИТЬ
После небольшого реального использования мне пока нравится следующее решение, которое заимствует некоторые аспекты решения 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 });
Это позволяет журналу знать имена переменных без какого-либо отдельного параметра для этой цели.
Мне тоже нравится этот ответ. По сравнению с моим ответом, это на кусочек больше работы по настройке, но даст лучший результат, поскольку каждая зависимость получает имя, тогда как мой говорит только о том, какой индекс изменился.
Вы можете переключиться с ссылки, содержащей true и false, на одну, содержащую null и {prevValue: value}, если вы хотите регистрировать старое значение, а также новое значение при его изменении.
Насколько я знаю, нет действительно простого способа сделать это из коробки, но вы можете добавить собственный хук, который отслеживает его зависимости и журналы, которые были изменены:
// 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.
В итоге я взял немного из разных ответов, чтобы сделать для этого свой собственный крючок. Я хотел иметь возможность просто добавить что-то вместо 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!
Это круто.
В «Здесь отп1 изменен и является вторым элементом в зависимости или индексом 1» есть небольшая, но важная опечатка — здесь должно быть написано dep2!
это каким-то образом должно быть поведением по умолчанию.
@simbathesailor/use-what-changed работает как часы!Install с npm/yarn и --dev или --no-saveimport { useWhatChanged } from '@simbathesailor/use-what-changed';
// (guarantee useEffect deps are in sync with useWhatChanged)
let deps = [a, b, c, d]
useWhatChanged(deps, 'a, b, c, d');
useEffect(() => {
// your effect
}, deps);
Создает эту красивую диаграмму в консоли:

Есть два распространенных виновника:
// 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
не знаю почему, но он ничего не регистрирует для меня
@JamilAlisgenderov Я думаю, что useWhatChanged должен использовать console.table .. поэтому, если вы пытаетесь протестировать в более старом браузере, который не поддерживает console.table, вы можете проверить, определено ли console.table. Вы также можете проверить нормальный console.info('что-то изменилось', 'таблица определена?', !!console.table); внутри ваших журналов ловушек useEffect. В противном случае... возможно, создайте проблему на github с вашей версией реакции + браузером.
@JamilAlisgenderov Когда-нибудь выясняли, что было причиной того, что use-what-changed ничего не регистрировал для вас?
кажется, это не поддерживается в сборнике рассказов
Просто мысль, если вам нужно проверить, какая переменная изменилась, не имеет ли смысл иметь несколько
useEffects(по одному для каждой изменяющейся переменной, которая может меняться независимо). Потому что ясно, что вы пытаетесь соединить два варианта использования в один?