React中的无限渲染问题总结

admin 2022年9月29日07:59:26评论8 views字数 4653阅读15分30秒阅读模式

React中的无限渲染问题总结

  • 前言

  • 无限渲染情况汇总分析

    • 第一种情况

    • 第二种情况

    • 第三种情况:state和setState分别在useEffect的依赖和回调中(前两种只与useState有关)

    • 第四种:缺失依赖

    • 第五种:函数(对象)作为依赖

    • 第六种:将数组(对象)作为依赖

    • 第七种:将对象作为依赖

  • 总结

  • 参考


前言

今天写代码的时候碰到了无限渲染问题,类似下图这样,栈崩掉了:

React中的无限渲染问题总结

是什么原因呢?

第一次渲染的时候num会变化,这会导致页面重新渲染----->第二次渲染,obj重新定义一次,但是这次的obj和上次的obj不是同一个(具体原因看下面例子,其实是引用发生了变化),这就导致useEffect里面的函数再次执行----->num再次变化---->无限循环

obj= {name:"jack"}
Obj= {name:"jack"}
console.log(obj === Obj);//false

又上网查了查,这类问题出现的情况还挺多,这里总结一下(想看结论直接看最后的总结)

无限渲染情况汇总分析

第一种情况

function App() {
const [count, setCount] = useState(0);

setCount(1); // infinite loop,注意,这里的setCount只要你加载页面就会自动调用,不需要你点击按钮来触发

return <div>hello</div>
}

// State updates → triggers re-render → state updates → triggers re-render → …
// 正确写法

function App() {
const [count, setCount] = useState(0);

useEffect(() => {
setCount(1);
}, [])


return <div>hello</div>
}

第二种情况

export default function App() {
const [count, setCount] = useState(0);

return (
<button onClick={setCount(1)}>Submit</button> // infinite loop,这里的setCount只要你加载页面就会自动调用,不需要你点击按钮来触发
);
}

// State updates → triggers re-render → state updates → triggers re-render → …

// 正确写法:

export default function App() {
const [count, setCount] = useState(0);

return (
<button onClick={() => setCount(1)}>Submit</button> //
);
}


第三种情况:state和setState分别在useEffect的依赖和回调中(前两种只与useState有关)

function App() {
const [count, setCount] = useState(0);

useEffect(() => {
setCount(count + 1) // infinite loop
}, [count])

return <div>hello</div>
}

// count updates → useEffect detects updated dependency → count updates → useEffect detects updated dependency → …



// 正确写法:

function App() {
const [count, setCount] = useState(0);

useEffect(() => {
setCount(previousCount => previousCount + 1)
}, [])

return <div>hello</div>
}


第四种:缺失依赖

useEffect(() => {
fetch("/api/user")
.then((res) => res.json)
.then((res) => {
setData(res);
});
});

// 正确
useEffect(() => {
fetch("/api/user")
.then((res) => res.json)
.then((res) => {
setData(res);
});
}, []); // <- dependencies

如果 useEffect 只有在依赖关系发生变化时才触发回调,那为什么我们在这里会出现无限循环?

你需要考虑到 React 的另一个重要的法则,即 “当 state 或 props 发生变化时,组件将重新渲染”。

在这段代码中,我们使用 setData 在网络调用成功后设置状态值,它将触发组件的重新渲染。由于 useEffect 没有值可以比较,所以它将调用回调(为什么?可以参考一这篇文章: https://mp.weixin.qq.com/s/0P7eWSNQNKWroDIlcgHBVw ,当然也可以自己去调试一下源代码去验证一下)。

第五种:函数(对象)作为依赖

useEffect的依赖为函数(或者说是对象的时候),要当心!(上面的图1提过)

import React, { useCallback, useEffect, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const getData = () => {
return window.localStorage.getItem("token");
};
const [dep, setDep] = useState(getData());
useEffect(() => {
setCount(count + 1);
}, [getData]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
<h2>Start editing toseesome magic happen!</h2>
</div>

);
}

// 正确写法:使用useCallback,
// useMemo能用么??考虑一下
const getData = useCallback(() => {
return window.localStorage.getItem("token");
}, []); // <- dependencies

函数 getData 作为依赖项被传入。

当你运行这段代码时,它将抛出 “超过最大更新” 的错误,这意味着代码有一个无限循环。

第六种:将数组(对象)作为依赖

import React, { useCallback, useEffect, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const dep = ["a"];
const [value, setValue] = useState(["b"]);
useEffect(() => {
setValue(["c"]);
}, [dep]);

return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
<h2>Start editing to see some magic happen!</h2>
</div>

);
}
// 正确写法:由于 useCallback 的返回是一个函数,我们不能使用。
// 我们需要使用另一个名为 useRef 的 hook
// useRef 返回一个可变的对象,.current 具有初始值。

export default function Home() {
const [value, setValue] = useState(["b"]);
const { current: a } = useRef(["a"]);
useEffect(() => {
setValue(["c"]);
}, [a])
}


第七种:将对象作为依赖

import React, { useCallback, useEffect, useState } from "react";
export default function App() {
const [count, setCount] = useState(0);
const data = {
is_fetched:false
};
useEffect(() => {
setCount(count + 1);
}, [data]);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
<h2>Start editing to see some magic happen!</h2>
</div>

);
}

当你运行这段代码时,你的浏览器控制台将被抛出一个无限循环的错误。

// 正确方案
import React, { useMemo, useEffect, useState } from "react";

export default function App() {
const [count, setCount] = useState(0);
const data = useMemo(
() => ({
is_fetched: false,
}),
[]
); // <- dependencies
useEffect(() => {
setCount(count + 1);
}, [data]);

return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => setCount(count + 1)}>{count}</button>
<h2>Start editing to see some magic happen!</h2>
</div>

);
}

总结

const [state,setState] = useState(initialState)

当使用useState时:

  • setState应该嵌入到函数中,以防止立即渲染所导致的无限渲染问题
  • state和setState分别在useEffect的依赖和回调中,会导致无限渲染问题

当你想要用useEffect时:

  • 没有依赖项,会导致无限渲染问题
  • 函数作为依赖,会导致无限渲染问题,应使用useCallback
  • 数组(对象)作为依赖,会导致无限渲染问题,应使用useRef
  • 对象作为依赖项,会导致无限渲染问题,应使用useMemo

useEffect的一些注意点:

  • 如果 useEffect 第二个参数传入 undefined 或者 null或者没有第二个参数,导致无限渲染

  • 如果传入了一个空数组,只会执行一次(一般在异步请求的时候这么设置)

  • 第二项为一个数组(正常情况),会对比数组中的每个元素有没有改变,来决定是否执行。

参考

https://javascript.plainenglish.io/5-useeffect-infinite-loop-patterns-2dc9d45a253f

https://alexsidorenko.com/blog/react-infinite-loop/

https://mp.weixin.qq.com/s/0P7eWSNQNKWroDIlcgHBVw


原文始发于微信公众号(迪哥讲事):React中的无限渲染问题总结

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年9月29日07:59:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   React中的无限渲染问题总结http://cn-sec.com/archives/1323199.html

发表评论

匿名网友 填写信息