在回调函数中使用 setState 挂钩时反应过多的重新渲染

Posted

技术标签:

【中文标题】在回调函数中使用 setState 挂钩时反应过多的重新渲染【英文标题】:React Too many re-renders when using setState hook inside callback function 【发布时间】:2020-09-07 08:32:06 【问题描述】:

当我在用作组件回调的函数中使用 setState 钩子时,我从 React 收到 Too many re-renders 错误。

下面是一个无用的示例来演示我正在尝试做的事情。

我有一个 Person 对象数组和一个 Pets 数组。每只宠物都有一个主人(=人)。 我想显示此人的姓名和他拥有的宠物。

我遍历 Persons 数组,每个人都应该有一个 TestComponent。 TestComponent 需要一个人的名字和一个宠物的名字。

宠物名称在回调函数getPetByOwnerName 中计算,它将遍历所有宠物,如果给定的人名与宠物的主人名匹配,它将返回该宠物名。

如果一个人没有宠物,它应该使用setPets()将一个空的宠物对象添加到宠物列表中。

getPetByOwnerName 函数内的setPets() 导致Too many re-renders 错误。

在这个例子中这样做可能没有任何意义,但它是为了重现我在一个更大的项目中遇到的问题。

import React,  useState  from 'react'
import TestComponent from './TestComponent'

const TestPage = () => 

    const [persons, setPersons] = useState([name: 'Tom', age: 35, name: 'Fred', age: 50 ])

    const [pets, setPets] = useState([owner: 'Tom',name: 'Doggo'])


    const getPetByOwnerName = (name) => 
        let petName = null
        pets.map((pet, index) => 
            if (pet.owner === name) 
                petName = pet.name
            
            return pet
        )

        if (!petName) 
            const pet = 
                owner: name,
                name: ''
            
            const updatedPets = pets
            pets.push(pet)
            setPets(updatedPets)
        

        return petName
    

    return (
        <React.Fragment>
            
                persons.map((person, index) => (
                    <TestComponent key=index personName=person.name petName=getPetByOwnerName(person.name)></TestComponent>
                ))
            
        </React.Fragment>
    )


export default TestPage

测试组件:

import React from 'react'

const TestComponent = ( personName, petName ) => 

    return (
        <React.Fragment>
            <p>personName owns pet: petName</p>
        </React.Fragment>
    )


export default TestComponent

【问题讨论】:

【参考方案1】:

在使用效果中处理这种功能可能会更好。您可以观察人员的变化,然后相应地更新宠物。如下所示。

但你的实际问题在这一行if (!petName)。当 petName 是一个空字符串时,它会进入这里并永远设置状态。你需要像if(!petName &amp;&amp; petName !== '')这样的支票

    useEffect(() => 
      persons.map(person => 
        const petIndex = pets.findIndex(p => p.owner === person.name);

          if (petIndex === -1) 
            const pet = 
              owner: person.name,
              name: ''
          
          setPets(prevPets => ([...prevPets, pet]))
        
      )
    , [persons]);

另外,在使用之前的状态更新状态时,最好使用回调版本(参见上面示例中更新的设置宠物)

由于这只是您的实际问题的一个示例,因此最好在您设置状态之前输入debugger;,并了解您为什么如此频繁地设置它。

【讨论】:

以上是关于在回调函数中使用 setState 挂钩时反应过多的重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

提交值时如何在 React 挂钩中使用回调函数?

反应钩子:新状态值未反映在 setInterval 回调中

在 HoC 中使用反应钩子时的警告

反应上下文(钩子)不更新所有引用

无法挂钩回调函数?

无效的挂钩调用。钩子只能在反应函数组件内部使用...... useEffect,redux