重复渲染
出现重复请求的情况
没有设置effect依赖参数
比如下面的例子,它在第一次渲染之后和每次更新之后都会执行。
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `You clicked ${count} times`;
})
这是因为每次重新渲染,都有它自己的 Props and State。每一个组件内的函数(包括事件处理函数,effects,定时器或者API调用等等)会捕获某次渲染中定义的props和state。某种意义上讲,effect 更像是渲染结果的一部分 ——每个 effect “属于”一次特定的渲染。
事实上这也正是我们可以在 effect 中获取最新的count的值,而不用担心其过期的原因。 如果是没有设置effect依赖参数的原因,在useEffect的第二个参数设置好依赖项就可以了。
设置的依赖频繁变化
有时候我们已经设置了依赖,但是发现还是会无限重复。有可能是你的依赖就是频繁变化的,即在改变状态的方法中用到了状态,比如:
import React, { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount(count + 1); // 这个 effect 依赖于 `count` state
}, 1000);
return () => clearInterval(id);
}, [count]);
return <h1>{count}</h1>;
}
export default Counter;
要解决这个问题,我们可以使用setState的函数式更新形式。它允许我们指定 state 该 如何 改变而不用引用 当前 state:
import React, { useState, useEffect } from "react";
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const id = setInterval(() => {
setCount((c) => c + 1); // ✅ 在这不依赖于外部的 `count` 变量
}, 1000);
return () => clearInterval(id);
}, []); // ✅ 我们的 effect 不适用组件作用域中的任何变量
return <h1>{count}</h1>;
}
export default Counter;
详细可以看官网的FAQ
设置的依赖是引用数据类型
如果我们设置的依赖是引用数据类型,我们会发现设置的依赖总是会改变。
比如下面这个例子,打开控制台,会看到至少2次输出。上面也提到了,每次重新渲染,函数组件都有它自己的 Props and State。因此,React在对比时会得出该依赖每次都不相同。即使看起来内容相同,但是每次的引用地址都不一样,即[] !== []。
import React, { useState, useEffect } from "react";
function Counter() {
const [data, setData] = useState([]);
useEffect(() => {
setTimeout(() => {
setData([]);
}, 100);
}, []);
useEffect(() => {
setTimeout(() => {
console.log(data);
}, 200);
}, [data]);
return <div />;
}
export default Counter;
解决方法:关于依赖项不要对React撒谎 如果你设置了依赖项,effect中用到的所有组件内的值都要包含在依赖中。这包括props,state,函数 — 组件内的任何东西。解决问题的方法不是移除依赖项。只有依赖项包含了所有effect中使用到的值,React才能知道何时需要运行它。
检查是不是必须把对象作为依赖
首先可以检查下是不是必须把该对象作为依赖,比如:
- 只需要用到该对象的某个非引用类型的属性
- 是JSON对象,可以通过
JSON.stringify()
转为字符串传递。子组件再将props传进来的JSON字符串用JSON.parse()
解析
useRef
如果上面的方法都无法解决,可以使用useRef。
我们知道,在useEffect的回调函数里读取最新的值而不是捕获的值,即从过去渲染中的函数里读取未来的props和state。
不同于useEffect捕获某次渲染中定义的props和state,useRef的.current属性就像一个保存一个可变值的“盒子”,可以获取最新的值。
而且当 ref 对象内容发生变化时,useRef并不会通知你。变更.current 属性不会引发组件重新渲染。
import React, { useState, useEffect, useRef } from "react";
function Counter() {
const [data, setData] = useState([]);
const dataRef = useRef(data);
useEffect(() => {
setTimeout(() => {
setData([]);
}, 100);
}, []);
useEffect(() => {
dataRef.current = data;
});
useEffect(() => {
setTimeout(() => {
console.log(dataRef.current);
}, 200);
}, []);
return <div />;
}
export default Counter;
链接
优秀文章