React报错Too many re-renders解决

 更新时间:2022年12月15日 17:08:24   作者:chuck  
这篇文章主要为大家介绍了React报错Too many re-renders解决,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

总览

产生"Too many re-renders. React limits the number of renders to prevent an infinite loop"错误有多方面的原因:

  • 在一个组件的渲染方法中调用一个设置状态的函数。
  • 立即调用一个事件处理器,而不是传递一个函数。
  • 有一个无限设置与重渲染的useEffect钩子。

too-many-re-renders-react-limits-the-number.png

这里有个示例来展示错误是如何发生的:

import {useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(0);
  // ⛔️ Too many re-renders. React limits the number
  // of renders to prevent an infinite loop.
  return (
    <div>
      <button onClick={setCounter(counter + 1)}>Increment</button>
      <h1>Count: {counter}</h1>
    </div>
  );
}

上述代码问题在于,我们在onClick事件处理器中立即调用了setCounter函数。

该函数是在页面加载时立即被调用,而不是事件触发后调用。

传递函数

为了解决该错误,为onClick事件处理器传递函数,而不是传递调用函数的结果。

import {useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(0);
  return (
    <div>
      <button onClick={() => setCounter(counter + 1)}>Increment</button>
      <h1>Count: {counter}</h1>
    </div>
  );
}

现在,我们为事件处理器传递了函数,而不是当页面加载时调用setCounter方法。

如果该方法在页面加载时被调用,就会触发一个setState动作,组件就会无限重新渲染。

如果我们试图立即设置一个组件的状态,而不使用一个条件或事件处理器,也会发生这个错误。

import {useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(0);
  // ⛔️ Too many re-renders. React limits the number
  // of renders to prevent an infinite loop.
  setCounter(counter + 1);
  return (
    <div>
      <h1>Count: {counter}</h1>
    </div>
  );
}

问题在于,setCounter函数在组件渲染时被调用、更新状态,并导致重新渲染,而且是无限重新渲染。

你可以通过向useState()钩子传递一个初始值或一个函数来初始化状态,从而解决这个错误。

import {useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(() => 100 + 100);
  return (
    <div>
      <h1>Count: {counter}</h1>
    </div>
  );
}

我们向useState方法传递了一个函数。这个函数只会在组件第一次渲染时被调用,并且会计算出初始状态。你也可以直接向useState方法传递一个初始值。

另外,你也可以像前面的例子那样使用一个条件或事件处理器。

import {useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(0);
  // 👇️ your condition here
  if (Math.random() > 0.5) {
    setCounter(counter + 1);
  }
  return (
    <div>
      <h1>Count: {counter}</h1>
    </div>
  );
}

如果你像上面的例子那样使用一个条件,请确保该条件不总是返回一个真值,因为这将导致无限的重新渲染循环。

"Too many re-renders. React limits the number of renders to prevent an infinite loop"错误也会在使用useEffect方法时发生,该方法的依赖会导致无限重新渲染。

import {useEffect, useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(0);
  useEffect(() => {
  // ⛔️ Too many re-renders. React limits the number
  // of renders to prevent an infinite loop.
    setCounter(counter + 1);
  }); // 👈️ forgot to pass dependency array
  return (
    <div>
      <h1>Count: {counter}</h1>
    </div>
  );
}

上述代码问题在于,我们没有为useEffect钩子传递依赖数组。

这意味着该钩子会在每次渲染时运行,它会更新组件的状态,然后无限重新运行。

传递依赖

解决该错误的一种办法是,为useEffect提供空数组作为第二个参数。

import {useEffect, useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    setCounter(counter + 1);
  }, []); // 👈️ empty dependencies array
  return (
    <div>
      <h1>Count: {counter}</h1>
    </div>
  );
}

如果你为useEffect方法传递空数组依赖作为第二个参数,该方法只在组件的初始渲染时运行。

该代码将计数器递增到1,并且不再运行,无论App组件是否被重新渲染。

如果你必须指定一个依赖来无限地重新渲染你的组件,试着寻找一个可以防止这种情况的条件。

import {useEffect, useState} from 'react';
export default function App() {
  const [counter, setCounter] = useState(0);
  useEffect(() => {
    // 👇️ some condition here
    if (Math.random() > 0.5) {
      setCounter(counter + 1);
    }
  }, [counter]);
  return (
    <div>
      <h1>Count: {counter}</h1>
    </div>
  );
}

有可能是某些逻辑决定了状态是否应该被更新,而状态不应该在每次重新渲染时被设置。

确保你没有使用一个在每次渲染时都不同的对象或数组作为useEffect钩子的依赖。

import {useEffect, useState} from 'react';
export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});
  const obj = {country: 'Chile', city: 'Santiago'};
  useEffect(() => {
    // ⛔️ Too many re-renders. React limits the number
    // of renders to prevent an infinite loop.
    setAddress(obj);
    console.log('useEffect called');
  }, [obj]);
  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

问题在于,在JavaScript中,对象是通过引用进行比较的。obj变量存储了一个具有相同键值对的对象,但每次渲染时的引用不同(在内存中的位置不同)。

移入依赖

解决该错误的一种办法是,把这个对象移到useEffect钩子里面,这样我们就可以把它从依赖数组中移除。

import {useEffect, useState} from 'react';
export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});
  useEffect(() => {
    // 👇️ move object inside of useEffect
    // and remove it from dependencies array
    const obj = {country: 'Chile', city: 'Santiago'};
    setAddress(obj);
    console.log('useEffect called');
  }, []);
  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

传递对象属性

另一个解决方案是将对象的属性传递给依赖数组。

import {useEffect, useState} from 'react';
export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});
  const obj = {country: 'Chile', city: 'Santiago'};
  useEffect(() => {
    setAddress({country: obj.country, city: obj.city});
    console.log('useEffect called');
    // 👇️ object properties instead of the object itself
  }, [obj.country, obj.city]);
  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

现在React不是在测试一个对象是否发生了变化,而是在测试obj.countryobj.city字符串在渲染之间是否发生了变化。

记忆值

另外,我们可以使用useMemo钩子来获得一个在不同渲染之间不会改变的记忆值。

import {useEffect, useMemo, useState} from 'react';
export default function App() {
  const [address, setAddress] = useState({country: '', city: ''});
  // 👇️ get memoized value
  const obj = useMemo(() => {
    return {country: 'Chile', city: 'Santiago'};
  }, []);
  useEffect(() => {
    setAddress(obj);
    console.log('useEffect called');
  }, [obj]);
  return (
    <div>
      <h1>Country: {address.country}</h1>
      <h1>City: {address.city}</h1>
    </div>
  );
}

我们将对象的初始化包裹在useMemo钩子里面,以获得一个不会在渲染之间改变的记忆值。

我们传递给useMemo钩子的第二个参数是一个依赖数组,它决定了我们传递给useMemo的回调函数何时被重新运行。

需要注意的是,数组在JavaScript中也是通过引用进行比较的。所以一个具有相同值的数组也可能导致你的useEffect钩子被无限次触发。

import {useEffect, useMemo, useState} from 'react';
export default function App() {
  const [nums, setNums] = useState([1, 2, 3]);
  const arr = [4, 5, 6];
  useEffect(() => {
    // ⛔️ Too many re-renders. React limits the number
    // of renders to prevent an infinite loop.
    setNums(arr);
    console.log('useEffect called');
  }, [arr]);
  return <div>{nums[0]}</div>;
}

数组在重新渲染之间存储相同的值,但指向内存中的不同位置,并且在每次组件重新渲染时有不同的引用。

在处理数组时,我们用于对象的方法同样有效。例如,我们可以使用useMemo钩子来获得一个在渲染之间不会改变的记忆值。

import {useEffect, useMemo, useState} from 'react';
export default function App() {
  const [nums, setNums] = useState([1, 2, 3]);
  const arr = useMemo(() => {
    return [4, 5, 6];
  }, []);
  useEffect(() => {
    setNums(arr);
    console.log('useEffect called');
  }, [arr]);
  return <div>{nums[0]}</div>;
}

我们将数组的初始化包裹在useMemo钩子里面,以获得一个不会在不同渲染之间改变的记忆值。

以上就是React报错Too many re-renders解决的详细内容,更多关于React报错Too many re-renders的资料请关注脚本之家其它相关文章!

相关文章

  • react如何实现筛选条件组件

    react如何实现筛选条件组件

    这篇文章主要介绍了react如何实现筛选条件组件问题,具有很好的参考价价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • React错误的习惯用法分析详解

    React错误的习惯用法分析详解

    这篇文章主要为大家介绍了React错误用法习惯分析详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • ReactNative短信验证码倒计时控件的实现代码

    ReactNative短信验证码倒计时控件的实现代码

    本篇文章主要介绍了ReactNative短信验证码倒计时控件的实现代码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-07-07
  • React安装node-sass失败解决方案分享

    React安装node-sass失败解决方案分享

    Node-sass是一个库,它将Node.js绑定到LibSass(流行样式表预处理器Sass的C版本),它允许用户以令人难以置信的速度将.scss文件本地编译为css,并通过连接中间件自动编译,下面这篇文章主要给大家介绍了关于React安装node-sass失败解决的相关资料,需要的朋友可以参考下
    2022-11-11
  • React和Vue的props验证示例详解

    React和Vue的props验证示例详解

    这篇文章主要介绍了React和Vue的props验证,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-08-08
  • ForwardRef useImperativeHandle方法demo

    ForwardRef useImperativeHandle方法demo

    这篇文章主要为大家介绍了ForwardRef useImperativeHandle方法demo,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-03
  • 基于React封装一个验证码输入控件

    基于React封装一个验证码输入控件

    邮箱、手机验证码输入是许多在线服务和网站常见的安全验证方式之一,本文主要来和大家讨论一下如何使用React封装一个验证码输入控件,感兴趣的可以了解下
    2024-03-03
  • react-redux中connect()方法详细解析

    react-redux中connect()方法详细解析

    connect()是React-redux中的核心方法之一,它将react组件预redux中的Store真正连接在一起,下面这篇文章主要给大家介绍了react-redux中connect()方法的相关资料,文中介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面来一起看看吧。
    2017-05-05
  • 一文详解React Redux设计思想与工作原理

    一文详解React Redux设计思想与工作原理

    最近看项目中使用了 Redux, 便尝试了解一波 Redux 的设计思想与工作原理,所以本文详细的给大家介绍了Redux设计思想与工作原理,需要的朋友可以参考下
    2023-09-09
  • webpack打包react项目的实现方法

    webpack打包react项目的实现方法

    这篇文章主要介绍了webpack打包react项目的实现方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06

最新评论