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

目录
相关文章
|
6月前
|
前端开发 JavaScript
React如何进行条件渲染
React如何进行条件渲染
43 0
|
28天前
|
前端开发 JavaScript
React学习之——条件渲染
【10月更文挑战第16天】React 中没有像Vue中v-if这种指令。React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
|
1月前
|
前端开发 JavaScript 容器
React 元素渲染
10月更文挑战第7天
25 1
|
1月前
|
监控 前端开发 UED
在 React 18 中利用并发渲染提高应用性能
【10月更文挑战第12天】利用并发渲染需要综合考虑应用的特点和需求,合理运用相关特性和策略,不断进行优化和调整,以达到最佳的性能提升效果。同时,要密切关注 React 的发展和更新,以便及时利用新的技术和方法来进一步优化应用性能。你还可以结合具体的项目实践来深入理解和掌握这些方法,让应用在 React 18 的并发渲染机制下发挥出更好的性能优势。
104 59
|
25天前
|
JavaScript 前端开发 算法
前端优化之超大数组更新:深入分析Vue/React/Svelte的更新渲染策略
本文对比了 Vue、React 和 Svelte 在数组渲染方面的实现方式和优缺点,探讨了它们与直接操作 DOM 的差异及 Web Components 的实现方式。Vue 通过响应式系统自动管理数据变化,React 利用虚拟 DOM 和 `diffing` 算法优化更新,Svelte 通过编译时优化提升性能。文章还介绍了数组更新的优化策略,如使用 `key`、分片渲染、虚拟滚动等,帮助开发者在处理大型数组时提升性能。总结指出,选择合适的框架应根据项目复杂度和性能需求来决定。
|
1月前
|
前端开发 JavaScript 算法
React 渲染优化策略
【10月更文挑战第6天】React 是一个高效的 JavaScript 库,用于构建用户界面。本文从基础概念出发,深入探讨了 React 渲染优化的常见问题及解决方法,包括不必要的渲染、大量子组件的渲染、高频事件处理和大量列表渲染等问题,并提供了代码示例,帮助开发者提升应用性能。
51 6
|
1月前
|
前端开发 JavaScript
React 条件渲染
10月更文挑战第9天
29 0
|
3月前
|
资源调度 前端开发 API
React Suspense与Concurrent Mode:异步渲染的未来
React的Suspense与Concurrent Mode是16.8版后引入的功能,旨在改善用户体验与性能。Suspense组件作为异步边界,允许子组件在数据加载完成前显示占位符,结合React.lazy实现懒加载,优化资源调度。Concurrent Mode则通过并发渲染与智能调度提升应用响应性,支持时间分片和优先级调度,确保即使处理复杂任务时UI仍流畅。二者结合使用,能显著提高应用效率与交互体验,尤其适用于数据驱动的应用场景。
72 20
|
3月前
|
前端开发
React 如何使用条件渲染
【8月更文挑战第17天】React 如何使用条件渲染
43 3
|
3月前
|
前端开发 JavaScript 中间件