• React useEffect()的无限循环是怎样来的
  • 发布于 1个月前
  • 81 热度
    0 评论
前言
我们都知道useEffect()用来引入具有「副作用」的操作,例如AJAX请求、DOM操作、启动与结束倒计时和监听与接触事件等,这些都可以在useEffect钩子去做。那么,是不是就真的那么简单的可以直接使用了呢?不是的,你可能会遇到一个陷阱,那就是组件渲染的无限循环,本文将为各位同学详细介绍无限循环的常见场景以及如何避免。

副作用
副作用指的是当调用函数时,除开返回函数值之外,还对主调用函数产生附加的影响。JS的部分内置函数是有副作用的,例如:
[1,2,3].pop(); // 执行完pop函数后,原数据会少一个元素
无限循环
下面我们通过几个例子来认识下useEffect的不恰当用法导致的无限循环;
缺失依赖
import React, { Fragment, useState } from 'react';

function countChange() {
 const [value, setValue] = useState('');
 const [count, setCount] = useState(0);
 useEffect(() => {
  setCount(count + 1);
 });
 return (
  <Fragment>
   <input
    type="text"
    value={value}
    onChange={({ target }) => {
     setValue(target.value);
    }}
   />
   <div>count is {count}</div>
  </Fragment>
  );
}
上述例子中,「input」框输入时会去更新value的值,这时候页面会重新渲染,因为「useEffect」没有依赖参数,这个时候便会每次渲染都会执行副作用回调,每次回调都会更新count,于是又会执行回调;陷入了无限循环。

这个时候,只需要给「useEffect」加上个依赖,只有value的值有更新的时候,才去执行副作用回调。避免了无限循环;依赖项为空数组时,代表只在初次渲染是调用一次;
useEffect(() => {
 setCount(count + 1);
}, [value]);
数组或对象作为依赖
「useEffect」只有当依赖发生改变时才会去触发回调,而且是通过浅层对象比较是否发生改变;那如果用对象或者数据作为依赖会发生什么呢?
import React, { Fragment, useState } from 'react';

function countChange() {
 const [value, setValue] = useState('');
 const [count, setCount] = useState(0);
 const dep = ['dep'];
 const obj = {
  name: 'pp',
 };
 // 使用数组作为依赖
 useEffect(() => {
  setCount(count + 1);
 }, [dep]);
 // 使用对象作为依赖
 useEffect(() => {
  setCount(count + 1);
 }, [obj]);
 return (
  <Fragment>
   <input
    type="text"
    value={value}
    onChange={({ target }) => {
     setValue(target.value);
    }}
   />
   <div>count is {count}</div>
  </Fragment>
 );
}
由于浅层对比的关系,比较的结果总是false,无论是数组还是对象作为依赖,都会一次又一次的触发回调;导致出现无限循环。数组作为对象可以通过「useRef」解决,更改引用本身不会触发组件重新渲染,相应代码改为:
const { current: dep } = useRef(['dep']);

useEffect(() => {
 setCount(count + 1);
}, [dep]);
对象作为对象可以通过「useMemo」解决,只有在依赖关系发生变化时才会重新计算记忆化的值。相应代码改为:
const obj = useMemo(() => ({
 name: 'pp',
}), [])

useEffect(() => {
 setCount(count + 1);
}, [obj]);
函数作为依赖
函数作为依赖项也是会导致无限循环的,这里不再贴代码;我们可以通过「useCallback」来解决;「useCallback」返回一个memoized版本的回调,只有在依赖关系改变时才会改变。
const func = useCallback(() => {
 return '1';
}, []);

总结
「useEffect」功能很强大,但是如果使用不当便会出现难以想象的问题,因此一定要正确使用「useEffect」。若useEffect的依赖数组的依赖值为Object、Array和Function等引用型数据,那么就需注意了。
用户评论