确定哪个依赖数组变量导致 useEffect 钩子触发

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

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

例如:

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

我当前的方法是一个接一个地删除依赖变量,直到我注意到导致过多 useEffect 调用的行为,但是一定有更好的方法来缩小这个范围。

47037 次浏览

更新

经过一些实际应用,我到目前为止喜欢下面的解决方案,它借用了 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

据我所知,没有一种真正简单的方法可以做到这一点,但是你可以放入一个自定义的钩子来跟踪它的依赖关系和日志中哪一个发生了变化:

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

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

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

最后,我从各种答案中选取了一些,以便为这个问题做出自己的答案。我希望能够删除一些东西来代替 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

例子一

以前:

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

之后:

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

控制台输出:

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

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

例子2

以前:

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

之后:

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

控制台输出:

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

这个图书馆... ... @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]
)
} />
}

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