乐趣区

关于前端:如何解决-ReactuseEffect-的无限循环

作者: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…)本人试试,以后输出 secretsecret.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… 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

退出移动版