React中的无限渲染问题总结

简介: React中的无限渲染问题总结前言无限渲染情况汇总分析第一种情况第二种情况第三种情况:state和setState分别在useEffect的依赖和回调中(前两种只与useState有关)第四种:缺失依赖第五种:函数(对象)作为依赖第六种:将数组(对象)作为依赖第七种:将对象作为依赖总结参考

React中的无限渲染问题总结


  • 前言
  • 无限渲染情况汇总分析
  • 第一种情况
  • 第二种情况
  • 第三种情况:state和setState分别在useEffect的依赖和回调中(前两种只与useState有关)
  • 第四种:缺失依赖
  • 第五种:函数(对象)作为依赖
  • 第六种:将数组(对象)作为依赖
  • 第七种:将对象作为依赖
  • 总结
  • 参考

前言

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

17.png

是什么原因呢?

第一次渲染的时候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>
}

第二种情况

exportdefaultfunction 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 → …
// 正确写法:
exportdefaultfunction 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";
exportdefaultfunction App() {
  const [count, setCount] = useState(0);
  const getData = () => {
    returnwindow.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(() => {
  returnwindow.localStorage.getItem("token");
}, []); // <- dependencies

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

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

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

import React, { useCallback, useEffect, useState } from"react";
exportdefaultfunction 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 具有初始值。
exportdefaultfunction Home() {
  const [value, setValue] = useState(["b"]);
  const { current: a } = useRef(["a"]);
  useEffect(() => {
    setValue(["c"]);
  }, [a])
}

第七种:将对象作为依赖

import React, { useCallback, useEffect, useState } from"react";
exportdefaultfunction 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";
exportdefaultfunction 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

目录
相关文章
|
1月前
|
前端开发 JavaScript
React如何进行条件渲染
React如何进行条件渲染
17 0
|
1月前
|
前端开发 JavaScript 算法
React渲染流程
应用的性能和灵活性,以后可以具体看一 Fiber 的工作原理。
22 3
|
23天前
|
前端开发 JavaScript
REACT 条件渲染
REACT 条件渲染
|
1月前
|
存储 前端开发 JavaScript
在回调函数中重新渲染React组件
在React中,重新渲染组件可通过`forceUpdate()`或`ReactDOM.render()`实现。方法一是使用`forceUpdate`强制无状态组件更新;方法二是通过重新创建根组件实例适用于有状态组件。这些示例基于Webpack和Babel的模块热替换配置。根据项目需求和React版本,还可以结合React-Router或Redux等库选择合适的方法。
|
1月前
|
存储 JavaScript 算法
React聚焦渲染速度
React聚焦渲染速度
28 0
|
1月前
|
存储 JSON 资源调度
next.js博客搭建_react-markdown渲染内容(第三步)
next.js博客搭建_react-markdown渲染内容(第三步)
16 1
|
1月前
|
数据采集 资源调度 前端开发
React的服务器端渲染:使用ReactDOMServer进行高效页面预渲染
【4月更文挑战第25天】使用ReactDOMServer,React支持服务器端渲染以实现高效预渲染。通过在Node.js环境中将React组件转化为HTML字符串,减少客户端JavaScript负载和渲染时间。优点包括更快首屏加载、改善SEO和兼容无JavaScript环境,但也会增加服务器负载、复杂性和状态管理挑战。开发者需根据项目需求平衡SSR和CSR。
|
1月前
|
前端开发 JavaScript
React中渲染html结构---dangerouslySetInnerHTML
React中渲染html结构---dangerouslySetInnerHTML
30 0
|
1月前
|
前端开发 JavaScript
React渲染性能的优化
React渲染性能的优化
33 2
|
1月前
|
前端开发
React 中条件渲染的 N 种方法
React 中条件渲染的 N 种方法
25 3