报错内容:The ‘chartInit’ function makes the dependencies of useEffect Hook (at line 38) change on every render.
如果你正在使用React Hooks,尤其是useEffect
这个Hook,你一定会遇到这个问题。
The ‘functionName’ function makes the dependencies of useEffect Hook (at line X) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of ‘functionName’ in its own useCallback() Hook. (react-hooks/exhaustive-deps)
将这个报错翻译为中文,大致意思是:
‘functionName’ 函数使 useEffect Hook(在第 X 行)的依赖关系在每次渲染时都发生变化。将它移到 useEffect 回调中。或者,将 ‘functionName’ 的定义包装在它自己的 useCallback() Hook 中。(react-hooks/exhaustive-deps)
其实,在这段报错中,已经告诉了我们报错的根本原因以及两种解决方法。我们逐步来看一下。
问题的根本原因
为了说明这个报错的原因,我们手写一个简单例子,来触发这个报错:
function App() {
const [count, setCount] = useState(0);
const logCount = () => {
console.log(count);
};
useEffect(() => {
logCount();
}, [logCount]);
return <div>{count}</div>;
}
在这个简单的
App
组件当中,存在一个自己的状态:count
,我们想要做的仅仅是,当count
状态改变的时候,都把当前的count
打印到控制台当中。
这个时候,控制台就会报一个这样的错误:
The ‘logCount’ function makes the dependencies of useEffect Hook (at line 10) change on every render. Move it inside the useEffect callback. Alternatively, wrap the definition of ‘logCount’ in its own useCallback() Hook. (react-hooks/exhaustive-deps).
这段代码中,useEffect
的依赖项是这个logCount
方法,我们一般会觉得,这个方法一直都没有被改变——渲染的都是相同的函数。但是,事实上真的是这样的吗??
我们先来看这样的两个例子:
console.log({ name: 'Donna' } === { name: 'Donna' });
// false
const fn1 = () => 'Donna';
const fn2 = () => 'Donna';
console.log(fn1 === fn2);
// false
其实,JavaScript 相等性是基于引用相等性工作的。也就是说,对象只有在它们引用内存中的相同对象时才彼此相等。这就是为什么我们最终会得到这样的结果。
这也就正好说明了,上面的useEffect
中的logCount
会在每个组件渲染中重新创建,因此始终与前一次渲染期间创建的上一个logCount
不相同。
那么,如何解决这个问题呢?
解决问题的方法
其实,在报错中已经告诉了我们如何解决这个问题。在这里,运用这个简单的小案例,来解决这个错误。
方法一:将它移到 useEffect 回调中
Move it inside the useEffect callback.
这个相当简单:我们获取logCount函数并将其移动到useEffect
钩子中。由于logCount
现在在useEffect
钩子中,我们不再需要它在依赖数组中。但是,由于logCount依赖于count变量
,我们必须将count变量添加到依赖数组中。
function App() {
const [count, setCount] = useState(1);
useEffect(() => {
const logCount = () => {
console.log(count);
};
logCount();
}, [count]);
return <div className="App">foo</div>;
}
方法二:将 ‘functionName’ 的定义包装在它自己的 useCallback() Hook 中
Alternatively, wrap the definition of ‘functionName’ in its own useCallback() Hook.
import { useState, useEffect, useCallback } from 'react';
function App() {
const [count, setCount] = useState(1);
const logCount = useCallback(() => {
console.log(count);
}, [count]);
useEffect(() => {
logCount();
}, [logCount]);
return <div className="App">foo</div>;
}
现在我们已经将logCount
函数包装在一个useCallback
钩子中,它将在每次渲染中保持相同的内存引用——除非它的依赖数组中的某些内容发生了变化。在这种情况下,我们添加count
到依赖数组。我们需要在计数更新时更新函数,否则我们的效果将无法运行。