【问题标题】:ReactJS: Maximum update depth exceeded errorReactJS:超过最大更新深度错误
【发布时间】:2019-10-23 22:03:33
【问题描述】:

我正在尝试在 ReactJS 中切换组件的状态,但我收到一条错误消息:

已超过最大更新深度。当组件在 componentWillUpdate 或 componentDidUpdate 中重复调用 setState 时,可能会发生这种情况。 React 限制了嵌套更新的数量以防止无限循环。

我的代码中没有看到无限循环,有人可以帮忙吗?

ReactJS 组件代码:

import React, { Component } from 'react';
import styled from 'styled-components';

class Item extends React.Component {
    constructor(props) {
        super(props);     
        this.toggle= this.toggle.bind(this);
        this.state = {
            details: false
        } 
    }  
    toggle(){
        const currentState = this.state.details;
        this.setState({ details: !currentState }); 
    }

    render() {
        return (
            <tr className="Item"> 
                <td>{this.props.config.server}</td>      
                <td>{this.props.config.verbose}</td> 
                <td>{this.props.config.type}</td>
                <td className={this.state.details ? "visible" : "hidden"}>PLACEHOLDER MORE INFO</td>
                {<td><span onClick={this.toggle()}>Details</span></td>}
            </tr>
    )}
}

export default Item;

【问题讨论】:

  • this.toggle() 更改为this.toggle{()=&gt; this.toggle()}
  • 另一个改进,虽然与您的问题无关:将toggle(){...} 转换为toggle = () =&gt; {...},这样您就不需要bind 它了!
  • 谢谢@learner。你也帮了我。您能否解释一下您的解决方案背后的原因。这两者有什么区别?
  • @Shamim 这是调用现有函数和将引用传递给函数之间的区别。了解我们正在编写在用户执行某项操作时显示和触发的代码,而不是在用户加载页面时立即触发的代码,这很有帮助。 reactjs.org/docs/faq-functions.html

标签: javascript reactjs react-native


【解决方案1】:

因为您在渲染方法中调用切换会导致重新渲染和切换将再次调用并再次重新渲染等等

代码中的这一行

{<td><span onClick={this.toggle()}>Details</span></td>}

你需要让onClick引用this.toggle而不是调用它

修复问题,这样做

{<td><span onClick={this.toggle}>Details</span></td>}

【讨论】:

  • 我也遇到了类似的情况,但是我需要传一个参数来切换,怎么实现呢?
  • @NivedithaKarmegam 做onClick={(param) =&gt; this.toggle(param)}。这不会立即触发(并重新渲染)。这是一个回调定义(箭头函数)。
  • @FabianPicone 尝试了您的方法,但是当我在 console.log 中显示“param”作为事件传递时,实际上应该执行onClick={() =&gt; this.toggle(param)}
  • @iWillGetBetter 是的 onClick 中的第一个参数是点击事件。如果你需要额外的参数,你也可以通过 onClick={(event) =&gt; this.toggle(event, myParam)}.
  • 我有这个函数closeEditModal = () =&gt; this.setState({openEditModal: false});render中怎么调用?
【解决方案2】:

你应该在调用函数时传递事件对象:

{<td><span onClick={(e) => this.toggle(e)}>Details</span></td>}

如果你不需要处理 onClick 事件,你也可以输入:

{<td><span onClick={(e) => this.toggle()}>Details</span></td>}

现在您还可以在函数中添加参数。

【讨论】:

  • 如果没有指定,则自动发送事件对象。只需在被调用的函数中包含一个输入参数。
  • {&lt;td&gt;&lt;span onClick={() =&gt; this.toggle(whateverParameter)}&gt;Details&lt;/span&gt;&lt;/td&gt;} 对我有用
  • 因为与事件对象无关的噪音而被否决。
【解决方案3】:

先忘记反应:
这与 React 无关,让我们了解 Java Script 的基本概念。例如,您在 java 脚本中编写了以下函数(名称为 A)。

function a() {

};

Q.1) 如何调用我们定义的函数?
答:a();

Q.2)如何传递函数的引用以便我们可以调用它?
Ans: 让 fun = a;

现在回答您的问题,您使用了带函数名称的括号,这意味着将在呈现以下语句时调用该函数。

&lt;td&gt;&lt;span onClick={this.toggle()}&gt;Details&lt;/span&gt;&lt;/td&gt;

那么如何纠正呢?
简单的!!只需删除括号。通过这种方式,您已经将该函数的引用提供给 onClick 事件。只有当你的组件被点击时它才会回调你的函数。

 &lt;td&gt;&lt;span onClick={this.toggle}&gt;Details&lt;/span&gt;&lt;/td&gt;

一个与反应相关的建议:
避免使用某人在答案中建议的内联函数,这可能会导致性能问题。 避免以下代码,每当调用函数时,它都会一次又一次地创建相同函数的实例(lamda 语句每次都会创建新实例)。
注意:并且不需要传递事件(e)明确的功能。您可以在函数中访问它而无需传递它。

{&lt;td&gt;&lt;span onClick={(e) =&gt; this.toggle(e)}&gt;Details&lt;/span&gt;&lt;/td&gt;}

https://cdb.reacttraining.com/react-inline-functions-and-performance-bdff784f5578

【讨论】:

    【解决方案4】:

    我知道这有很多答案,但由于它们中的大多数都是旧的(嗯,旧的),所以没有人提到我很快就变得非常喜欢的方法。简而言之:

    使用功能组件和钩子

    更长的时间:

    尝试使用尽可能多的功能组件而不是类组件尤其是用于渲染,并尽量保持它们尽可能纯净(是的,我知道默认情况下数据是脏的)。

    功能组件的两个显而易见的好处(还有更多):

    • 纯度或接近纯度使调试变得如此容易
    • 功能组件不再需要构造函数锅炉代码

    第二点的快速证明 - 这不是很恶心吗?

    constructor(props) {
            super(props);     
            this.toggle= this.toggle.bind(this);
            this.state = {
                details: false
            } 
        }  
    

    如果您将功能组件用于渲染之外,您将需要伟大二重奏的第二部分 - 钩子。为什么它们比生命周期方法更好,它们还能做什么以及更多内容会占用我很多空间来介绍,所以我建议你听听这个人自己的意见:Dan preaching the hooks

    在这种情况下,您只需要两个钩子:

    方便地命名为useCallback 的回调挂钩。这样,您就可以防止在重新渲染时一遍又一遍地绑定函数。

    一个名为useState 的状态钩子,用于在整个组件正常运行并完整执行的情况下保持状态(是的,由于钩子的魔力,这是可能的)。在该挂钩中,您将存储切换的值。

    如果你读到这部分,你可能想看看我所说的一切在行动中并应用于原始问题。干得好: Demo

    对于那些只想看一眼组件和 WTF 的人来说,这里是:

    const Item = () => {
    
        // HOOKZ
      const [isVisible, setIsVisible] = React.useState('hidden');
    
      const toggle = React.useCallback(() => {
        setIsVisible(isVisible === 'visible' ? 'hidden': 'visible');
      }, [isVisible, setIsVisible]);
    
        // RENDER
      return (
      <React.Fragment>
        <div style={{visibility: isVisible}}>
            PLACEHOLDER MORE INFO
        </div>
        <button onClick={toggle}>Details</button>
      </React.Fragment>
      )
    };
    

    PS:我写这篇文章是为了防止很多人在这里遇到类似的问题。希望他们会喜欢我在这里展示的内容,至少可以再用谷歌搜索一下。这不是我说其他答案是错误的,而是我说自从他们写出来之后,还有另一种方法(恕我直言,更好的方法)来处理这个问题。

    【讨论】:

      【解决方案5】:

      如果您不需要将参数传递给函数,只需从函数中删除 (),如下所示:

      <td><span onClick={this.toggle}>Details</span></td>
      

      但是如果你想传递参数,你应该像下面这样:

      <td><span onClick={(e) => this.toggle(e,arg1,arg2)}>Details</span></td>
      

      【讨论】:

        【解决方案6】:

        1.如果我们想在调用中传递参数,那么我们需要调用如下方法 因为我们使用的是箭头函数,所以不需要绑定constructor中的方法。

        onClick={() => this.save(id)} 
        

        当我们像这样在构造函数中绑定方法时

        this.save= this.save.bind(this);
        

        那么我们需要在不传递任何参数的情况下调用该方法,如下所示

        onClick={this.save}
        

        我们尝试在调用函数时传递参数 如下所示,错误就像超出了最大深度。

         onClick={this.save(id)}
        

        【讨论】:

        • 在使用“bind”时,我们如何向函数传递参数?
        【解决方案7】:

        在这种情况下,这段代码

        {<td><span onClick={this.toggle()}>Details</span></td>}
        

        导致切换函数立即调用并一次又一次地重新渲染它,从而进行无限调用。

        所以只传递对该切换方法的引用将解决问题。

        所以,

        {<td><span onClick={this.toggle}>Details</span></td>}
        

        将是解决方案代码。

        如果你想使用 () ,你应该使用这样的箭头函数

        {<td><span onClick={()=> this.toggle()}>Details</span></td>}
        

        如果你想传递参数,你应该选择最后一个选项,你可以像这样传递参数

        {<td><span onClick={(arg)=>this.toggle(arg)}>Details</span></td>}
        

        在最后一种情况下,它不会立即调用,也不会导致函数的重新渲染,因此避免了无限调用。

        【讨论】:

        • 非常好的解释。非常感谢
        【解决方案8】:

        ReactJS:超出最大更新深度错误

        inputDigit(digit){
          this.setState({
            displayValue: String(digit)
          })
        
        <button type="button"onClick={this.inputDigit(0)}>
        

        为什么会这样?

        <button type="button"onClick={() => this.inputDigit(1)}>1</button>
        

        函数 onDigit 设置状态,这会导致重新渲染,这 导致 onDigit 触发,因为这是您设置的值 onClick 会导致设置状态,从而导致重新渲染, 这会导致 onDigit 触发,因为这就是你的价值……等等

        【讨论】:

          【解决方案9】:

          onClick你应该调用函数,这就是你的函数切换。

          onClick={() =&gt; this.toggle()}

          【讨论】:

            【解决方案10】:

            最近我收到了这个错误:

            错误:缩小的 React 错误 #185;访问https://reactjs.org/docs/error-decoder.html?invariant=185 获取完整消息或使用非缩小开发环境获取完整错误和其他有用的警告。

            你刚才遇到的错误全文是:

            已超过最大更新深度。当组件在 componentWillUpdatecomponentDidUpdate 中重复调用 setState 时,可能会发生这种情况。 React 限制了嵌套更新的数量以防止无限循环。

            好的。这是我的情况,我使用反应函数组件 + 反应钩子。我们先看看不正确的示例代码:

            import { useEffect, useState } from "react";
            const service = {
              makeInfo(goods) {
                if (!goods) return { channel: "" };
                return { channel: goods.channel };
              },
              getGoods() {
                return new Promise((resolve) => {
                  setTimeout(() => {
                    resolve({
                      channel: "so",
                      id: 1,
                      banners: [{ payway: "visa" }, { payway: "applepay" }]
                    });
                  }, 1000);
                });
              },
              makeBanners(info, goods) {
                if (!goods) return [];
                return goods.banners.map((v) => {
                  return { ...v, payway: v.payway.toUpperCase() };
                });
              }
            };
            export default function App() {
              const [goods, setGoods] = useState();
              const [banners, setBanners] = useState([]);
            
              useEffect(() => {
                service.getGoods().then((res) => {
                  setGoods(res);
                });
              }, []);
            
              const info = service.makeInfo(goods);
            
              useEffect(() => {
                console.log("[useEffect] goods: ", goods);
                if (!goods) return;
                setBanners(service.makeBanners({}, goods));
              }, [info, goods]);
            
              return <div>banner count: {banners.length}</div>;
            }
            

            service - 处理 API 调用,并有一些方法用于转换 DTO 数据视图模型。它与 React 无关。也许你的项目中有这样的服务。

            我的逻辑是 banners 视图模型是从 API 返回的 goods 数据构造的。

            useEffect({...}, [info, goods]) 有两个依赖项:infogoods

            infogoods改变时,useEffect钩子会重新执行,设置banners view model,看起来不错吧?

            不行!这会导致内存泄漏。 useEffect 钩子将无限执行。为什么?

            因为setBanner()执行时,组件会重新渲染,const info = service.makeInfo(goods);语句会再次执行,返回一个新的info对象,这会导致@的变化987654339@的deps,导致useEffect再次执行,形成死循环。

            解决方案:使用useMemo返回一个记忆值。将此记忆值用作useEffect钩子的依赖项。

            // ...
             const info = useMemo(() => {
                return service.makeInfo(goods);
              }, [goods]);
            
              useEffect(() => {
                console.log("[useEffect] goods: ", goods);
                if (!goods) return;
                setBanners(service.makeBanners({}, goods));
              }, [info, goods]);
            
            //... 
            

            Codesandbox

            【讨论】:

              【解决方案11】:

              许多很好的答案,但都缺少一些考虑到 react/React native 中的钩子的示例

              正如上面的答案中所写,回调不应在子组件内“调用”,而只能被引用。

              这是什么意思? 让我们考虑一个父组件,它带有 2 个子组件,它们改变 rgb 颜色以在按下时改变颜色:

              import React, { useState } from "react"
              import {View, Text, StyleSheet } from "react-native"
              import ColorCounter from "../components/ColorCounter"
              
              const SquareScreen = () =>{
                const [red, setRed] = useState(0)
                const [blue, setBlue] = useState(0)
                const [green, setGreen] = useState(0)
              
               return (
                 <View>
                   <ColorCounter 
                     onIncrease={() => setRed(red + 15)}
                     onDecrease={() => setRed(red - 15)}
                     color="Red"
                   />
                   <ColorCounter 
                     onIncrease={() => setBlue(blue + 15)}
                     onDecrease={() => setBlue(blue - 15)} 
                     color="Blue" 
                   />
                   <ColorCounter 
                     onIncrease={() => setGreen(green + 15)}
                     onDecrease={() => setGreen(green - 15)}
                     color="Green" 
                   />
                  <View 
                    style={{ 
                      height:150,
                      width:150, 
                      backgroundColor:`rgb(${red},${blue},${green})`
                    }}
                  />
                  </View>
               )   
              }
              
              const styles = StyleSheet.create({})
              
              export default SquareScreen
              

              这是子按钮组件:

              import React, { useState } from "react"
              import {View, Text, StyleSheet, Button } from "react-native"
              
              const ColorCounter = ({color, onIncrease, onDecrease}) =>{
                return (
                  <View>
                    <Text>{color}</Text>
                    <Button onPress={onIncrease}  title={`Increase ${color}`} /> --> here if you use onPress={onIncrease()} this would cause a call of setColor(either setRed,SetBlue or setGreen) that call again onIncrease and so on in a loop)
                    <Button onPress={onDecrease}  title={`Decrease ${color}`} />
                  </View>  
                )  
              }
              
              export default ColorCounter
              

              【讨论】:

                猜你喜欢
                • 2020-01-04
                • 1970-01-01
                • 2020-07-15
                • 1970-01-01
                • 2021-01-21
                • 2021-05-15
                • 2020-12-17
                • 2021-06-20
                相关资源
                最近更新 更多