作者:Shadeed
译者:前端小智
起源:dmitripavlutin
有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。
useEffect()
次要用来治理副作用,比方通过网络抓取、间接操作 DOM、启动和完结计时器。
尽管 useEffect()
和 useState
(治理状态的办法) 是最罕用的钩子之一,但须要一些工夫来相熟和正确应用。
应用 useEffect()
时,你可能会遇到一个陷阱,那就是组件渲染的有限循环。在这篇文章中,会讲一下产生有限循环的常见场景以及如何防止它们。
1. 有限循环和副作用更新状态
假如咱们有一个性能组件,该组件外面有一个 input
元素,组件是性能是计算 input
更改的次数。
咱们给这个组件取名为 CountInputChanges
,大略的内容如下:
function CountInputChanges() {const [value, setValue] = useState('');
const [count, setCount] = useState(-1);
useEffect(() => setCount(count + 1));
const onChange = ({target}) => setValue(target.value);
return (
<div>
<input type="text" value={value} onChange={onChange} />
<div>Number of changes: {count}</div>
</div>
)
}
<input type =“text”value = {value} onChange = {onChange} />
是受控组件。value
变量保留着 input
输出的值,当用户输出输出时,onChange
事件处理程序更新 value
状态。
这里应用 useEffect()
更新 count
变量。每次因为用户输出而导致组件从新渲染时,useEffect(() => setCount(count + 1))
就会更新计数器。
因为 useEffect(() => setCount(count + 1))
是在没有依赖参数的状况下应用的,所以 ()=> setCount(count + 1)
会在每次渲染组件后执行回调。
你感觉这样写会有问题吗?关上演示本人试试看:https://codesandbox.io/s/infi…
运行了会发现 count
状态变量不受管制地减少,即便没有在 input
中输出任何货色,这是一个有限循环。
问题在于 useEffect()
的应用形式:
useEffect(() => setCount(count + 1));
它生成一个有限循环的组件从新渲染。
在初始渲染之后,useEffect()
执行更新状态的副作用回调函数。状态更新触发从新渲染。从新渲染之后,useEffect()
执行副作用回调并再次更新状态,这将再次触发从新渲染。
1.1 通过依赖来解决
有限循环能够通过正确治理 useEffect(callback, dependencies)
依赖项参数来修复。
因为咱们心愿 count
在值更改时减少,所以能够简略地将 value
作为副作用的依赖项。
import {useEffect, useState} from 'react';
function CountInputChanges() {const [value, setValue] = useState('');
const [count, setCount] = useState(-1);
useEffect(() => setCount(count + 1), [value]);
const onChange = ({target}) => setValue(target.value);
return (
<div>
<input type="text" value={value} onChange={onChange} />
<div>Number of changes: {count}</div>
</div>
);
}
增加 [value]
作为 useEffect
的依赖,这样只有当 [value]
发生变化时,计数状态变量才会更新。这样做能够解决有限循环。
1.2 应用 ref
除了依赖,咱们还能够通过 useRef() 来解决这个问题。
其思维是更新 Ref 不会触发组件的从新渲染。
import {useEffect, useState, useRef} from "react";
function CountInputChanges() {const [value, setValue] = useState("");
const countRef = useRef(0);
useEffect(() => countRef.current++);
const onChange = ({target}) => setValue(target.value);
return (
<div>
<input type="text" value={value} onChange={onChange} />
<div>Number of changes: {countRef.current}</div>
</div>
);
}
useEffect(() => countRef.current++)
每次因为 value
的变动而从新渲染后,countRef.current++
就会返回。援用更改自身不会触发组件从新渲染。
2. 有限循环和新对象援用
即便正确设置了 useEffect()
依赖关系,应用对象作为依赖关系时也要小心。
例如,上面的组件 CountSecrets
监听用户在 input
中输出的单词,一旦用户输出非凡单词'secret'
,统计 ‘secret’ 的次数就会加 1。
import {useEffect, useState} from "react";
function CountSecrets() {const [secret, setSecret] = useState({value: "", countSecrets: 0});
useEffect(() => {if (secret.value === 'secret') {setSecret(s => ({...s, countSecrets: s.countSecrets + 1})); }
}, [secret]);
const onChange = ({target}) => {setSecret(s => ({ ...s, value: target.value}));
};
return (
<div>
<input type="text" value={secret.value} onChange={onChange} />
<div>Number of secrets: {secret.countSecrets}</div>
</div>
);
}
关上演示(https://codesandbox.io/s/infi…)本人试试,以后输出 secret
,secret.countSecrets
的值就开始不受管制地增长。
这是一个有限循环问题。
为什么会这样?
secret
对象被用作useEffect(..., [secret])
。在副作用回调函数中,只有输出值等于secret
,就会调用更新函数
setSecret(s => ({...s, countSecrets: s.countSecrets + 1}));
这会减少 countSecrets
的值,但也会创立一个新对象。
secret
当初是一个新对象,依赖关系也产生了变动。所以 useEffect(..., [secret])
再次调用更新状态和再次创立新的 secret
对象的副作用,以此类推。
JavaScript 中的两个对象只有在援用完全相同的对象时才相等。
2.1 防止将对象作为依赖项
解决由循环创立新对象而产生的有限循环问题的最好办法是防止在 useEffect()
的dependencies
参数中应用对象援用。
let count = 0;
useEffect(() => {// some logic}, [count]); // Good!
let myObject = {prop: 'Value'};
useEffect(() => {// some logic}, [myObject]); // Not good!
useEffect(() => {// some logic}, [myObject.prop]); // Good!
修复 <CountSecrets>
组件的有限循环问题,能够将useEffect(..., [secret]))
变为 useEffect(..., [secret.value])
。
仅在 secret.value
更改时调用副作用回调就足够了,上面是修复后的代码:
import {useEffect, useState} from "react";
function CountSecrets() {const [secret, setSecret] = useState({value: "", countSecrets: 0});
useEffect(() => {if (secret.value === 'secret') {setSecret(s => ({...s, countSecrets: s.countSecrets + 1}));
}
}, [secret.value]);
const onChange = ({target}) => {setSecret(s => ({ ...s, value: target.value}));
};
return (
<div>
<input type="text" value={secret.value} onChange={onChange} />
<div>Number of secrets: {secret.countSecrets}</div>
</div>
);
}
3 总结
useEffect(callback, deps)
是在组件渲染后执行 callback(副作用)
的 Hook。如果不留神副作用的作用,可能会触发组件渲染的有限循环。
生成有限循环的常见状况是在副作用中更新状态,没有指定任何依赖参数
useEffect(() => {
// Infinite loop!
setState(count + 1);
});
防止有限循环的一种无效办法是正确设置依赖项:
useEffect(() => {
// No infinite loop
setState(count + 1);
}, [whenToUpdateValue]);
另外,也能够应用 Ref,更新 Ref 不会触发从新渲染:
useEffect(() => {
// No infinite loop
countRef.current++;
});
有限循环的另一种常见办法是应用对象作为 useEffect()
的依赖项,并在副作用中更新该对象(无效地创立一个新对象)
useEffect(() => {
// Infinite loop!
setObject({
...object,
prop: 'newValue'
})
}, [object]);
防止应用对象作为依赖项,只应用特定的属性(最终后果应该是一个原始值):
useEffect(() => {
// No infinite loop
setObject({
...object,
prop: 'newValue'
})
}, [object.whenToUpdateProp]);
当应用 useEffect()
时,你还晓得有其它形式会引起有限循环陷阱吗?
~ 完,我是小智,咱们下期见~
代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。
原文:https://dmitripavlutin.com/re…
交换
有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。
本文 GitHub https://github.com/qq44924588… 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。