目录
当你在React中从使用类组件转移到功能组件时,可能会很颠簸。你必须学习如何使用React Hooks。最有可能的是,你学到的第一个是useState 。那里没有什么大问题,因为它的工作原理与setState 。
但后来你必须根据道具来更新状态,这意味着是时候尝试一下useEffect 。这看起来也很简单,一开始:你读了关于依赖数组的内容,这很有意义。但是在使用useEffect ,也很容易走错路。让我们看一个例子。
const App = () => {
const [randomNumber, setRandomNumber] = useState();
React.useEffect(() => {
setRandomNumber(Math.random());
}, [randomNumber]);
}
上面的代码中的错误是非常明显的,你可以在这里看到结果。上面的代码创造了一个无尽的循环。但是,你使用useEffect 的另一种错误方式是创建一个陈旧的闭包,这可能更难发现,并导致可能难以追踪的问题。
当useEffect 中的一个变量从未更新时就会发生这种情况。有时这是你想要的,但大多数情况下,你并不想要。下面是一个陈旧闭合的例子。
const App() {
const [count, setCount] = useState(0);
useEffect(function () {
setInterval(function log() {
console.log(`Count is: ${count}`);
}, 2000);
}, []);
return (
<div className="App">
{count}
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
你可以在这里看到这段代码的运行。当你点击按钮时,显示在组件中的count 变量会更新,但每两秒钟被记录在useEffect 的值一直保持在0 。我们正期待着这一点,但随着你的代码变得越来越复杂,可能会更难找到问题所在。幸运的是,你可以使用ESlint插件来为你找到它,在它成为一个bug之前。
什么是详尽的deps lint规则?
如果你把鼠标悬停在代码例子中的依赖性数组下的斜线上,你会看到lint为什么会生气。

你会看到,lint给了你几个选择:要么把count ,要么把依赖性数组完全删除。如果你删除了依赖性数组,里面的函数将在每次渲染时运行。我想这很好,但这违背了最初设置useEffect 的目的。
明显的答案是将count 变量添加到依赖阵列中。在VS Code中,通过ESlint扩展,以及在其他具有类似功能的IDE中,你可以点击快速修复链接,count 将为你添加到依赖数组中。
提示是非常有争议性的。大多数开发者认为应该这样做,但他们对如何做有很大分歧。许多规则,如关于缩进、大括号的间距和其他规则,更多的是关于可读性,而不是关于确保良好的代码。
但 [eslint-plugin-react-hooks](https://www.npmjs.com/package/eslint-plugin-react-hooks)可以让你不至于在React Hooks上犯错,而这些错误是很难追踪和调试的。这个ESlint插件将确保你在React代码中遵循Hooks的规则,这就是。
- 只在顶层调用Hooks
- 只从React函数中调用Hooks
它还会检查你的Hooks中的依赖数组,以确保你从它们那里得到你期望的功能。
如何在React项目中添加这一规则
如果你使用Create React App,React Hooks的ESlint插件已经默认包含。要把它添加到现有的项目中,只需用npm或yarn安装它。
npm install eslint-plugin-react-hooks --save-dev
yarn add eslint-plugin-react-hooks --dev
接下来,在你的ESlint配置中添加以下内容。
// ESLint configuration
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // For checking rules of hooks
"react-hooks/exhaustive-deps": "warn" // For checking hook dependencies
}
}
你还需要在你的IDE中添加ESlint扩展,以便更容易纠正警告。对于VS Code,你可以使用这个扩展,对于Atom,你可以使用这个。
如何修复详尽的部署警告
一旦你在ESlint配置中加入了详尽的deps规则,你可能会遇到一些警告,这些警告可能需要比我们第一个例子更多的思考。你可以在GitHub上找到一长串关于这个规则的评论,在这些评论中,使用这个规则的开发者并不清楚为什么他们会得到一个警告,或者如何解决这个问题。
我们得到的第一个详尽的deps警告是因为依赖关系数组中缺少一个原始变量。
const App() {
const [count, setCount] = useState(0);
useEffect(function () {
console.log(`Count is: ${count}`);
}, []);
return (
<div className="App">
{count}
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
要知道为什么会有警告,以及为什么你在IDE中点击快速修复得到的修复会起作用,这很简单。添加计数变量会解决这个问题,不会引起任何奇怪的问题。但有时,在使用建议的解决方案之前,必须对其进行检查。
使用对象和数组
export default function App() {
const [address, setAddress] = useState({ country: "", city: "" });
const obj = { country: "US", city: "New York" };
useEffect(() => {
setAddress(obj);
}, []);
return (
<div>
<h1>Country: {address.country}</h1>
<h1>City: {address.city}</h1>
</div>
);
}
你可以在这里看到这段代码的实况。注意有一个关于依赖数组需要obj 变量的警告。很奇怪!它总是相同的值,那么为什么会发生这种情况呢?
JavaScript中的对象和数组是通过引用来比较的,而不是通过值。每次组件渲染时,这个对象有相同的值,但有不同的引用。如果这个变量是一个数组,同样的事情也会发生。
为了消除警告,你可以把obj 变量添加到数组中,但这意味着useEffect 中的函数将在每次渲染时运行。这也是点击 "快速修复"所要做的,而不是你真正想要的应用程序的运作方式。一个解决方案是在依赖性数组中使用对象的一个属性。
useEffect(() => {
setAddress(obj);
}, [obj.city]);
另一个选择是使用useMemo 钩子来获得该对象的一个记忆值。
const obj = useMemo(() => {
return { country: 'US', city: 'New York' };
}, []);
你也可以把obj 变量移到useEffect ,或者移到组件外,以消除警告,因为在这些位置,它不会在每次渲染时被重新创建。
// Move it here
// const obj = { country: "US", city: "New York" };
export default function App() {
const [address, setAddress] = useState({ country: "", city: "" });
const obj = { country: "US", city: "New York" };
useEffect(() => {
// Or here
// const obj = { country: "US", city: "New York" };
setAddress(obj);
}, []);
return (
<div>
<h1>Country: {address.country}</h1>
<h1>City: {address.city}</h1>
</div>
);
}
上面的例子是为了说明在依赖阵列警告似乎没有意义的情况下,修复它的可能方法。下面是一个你可能想在你的代码中注意的例子。
你确实想在不禁用lint规则的情况下解决这个警告,但是,就像上面的例子一样,使用lint建议会导致useEffect 中的函数不必要地运行。在这个例子中,我们有道具被传入一个组件。
import { getMembers } from '../api';
import Members from '../components/Members';
const Group = ({ group }) => {
const [members, setMembers] = useState(null);
useEffect(() => {
getMembers(group.id).then(setMembers);
}, [group]);
return <Members group={group} members={members} />
}
如果group 道具是一个对象,React会检查当前的渲染是否指向先前渲染中的同一个对象。因此,即使对象是相同的,如果为后续渲染创建了一个新的对象,useEffect 。
我们可以通过检查group 对象中的特定属性来解决这个问题,因为我们知道这个属性会发生变化。
useEffect(() => {
getMembers(group.id).then(setMembers);
}, [group.id]);
如果任何一个值可能发生变化,你也可以使用useMemo Hook。
import { getMembers, getRelatedNames } from '../api';
import Members from '../components/Members';
const Group = ({ id, name }) => {
const group = useMemo(() => () => return { id, name }, [
id,
name,
]);
const [members, setMembers] = useState(null);
const [relatedNames, setRelatedNames] = useState(null);
useEffect(() => {
getMembers(id).then(setMembers);
getRelatedNames(names).then(setRelatedNames);
}, []);
return <Members group={group} members={members} />
}
在这里,当id 或name 的值发生变化时,group 变量将被更新,而useEffect 只有在group 常数发生变化时才会运行。我们也可以简单地将这些值添加到依赖关系数组中,而不是使用useMemo 。
处理缺失的函数
有的时候,lint警告会告诉你在一个数组中缺少一个函数。这将发生在任何有可能关闭超过状态的时候。下面是一个例子。
const App = ({ data }) => {
const logData = () => {
console.log(data);
}
useEffect(() => {
logData();
}, [])
}
这段代码会有一个lint警告,你可以在这里看到,建议你把logData 加入到依赖数组中。这是因为它使用了data 这个道具,这个道具可能会改变。为了解决这个问题,你可以按照建议把它添加到依赖关系数组中,或者,这样做。
const App = ({ data }) => {
useEffect(() => {
const logData = () => {
console.log(data);
}
logData();
}, [])
}
结论
由于React Hooks的复杂性和难以发现的问题,如果你在开发你的应用程序时走错了路,你可能会产生这样的问题,在Hooks规则中加入详尽的deps lint规则是必要的。这些规则将节省你的时间,并立即告诉你什么时候做错了。
有了正确的IDE扩展,甚至可以为你修复问题。虽然通常 "快速修复 "的解决方案是有效的,但你应该始终检查你的代码,了解为什么会有警告。这样,如果你想防止不必要的渲染,你会找到最好的修复方法,并可能找到一个更好的解决方案。