Skip to main content

重复渲染

出现重复请求的情况

没有设置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;

链接

优秀文章