You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React状态提升与修改器传递时useEffect触发无限循环问题

解决React组件useEffect无限循环问题(因匿名onChange函数导致)

问题场景

子组件DamageInput通过useEffect监听内部状态变化,调用父组件传入的onChange回调同步状态:

export default function DamageInput({ data, onChange }){

    const [qty, setQty] = useState(data?.qty || null);
    const [selectedSize, setSelectedSize] = useState(data?.size || null);
    const [selectedSuppl, setSelectedSuppl] = useState(data?.suppl || []);

    useEffect(() => {

        if(!selectedSize) onChange(null);

        else {

            const damageObject = {
                
                qty: qty,
                size: selectedSize,
                suppl: selectedSuppl
            }

            onChange(damageObject);

        }

    }, [selectedSize, qty, selectedSuppl, onChange]);

    ...
}

父组件通过匿名函数传递onChange:

<DamageInput onChange={(damageObject) => handleDamageChange('bonnet', damageObject)} data={data.damage.bonnet} />

父组件的状态更新函数:

const handleDamageChange = (panel, damageObject) => {

    if(!damageObject){

        if(data.damage[panel]){

            setData(prevState => {

                const damageCopy = {...prevState.damage};
                delete damageCopy[panel];

                return {...prevState, damage: damageCopy};

            });

        }

    }

    else setData(prevState => ({...prevState, damage: {...prevState.damage, [panel]: damageObject}}));

};

问题现象:子组件useEffect触发无限循环。
原因分析:父组件传递的onChange是匿名函数,每次父组件渲染都会生成新的函数实例。子组件useEffect依赖onChange,导致每次父组件更新后,子组件useEffect都会重新执行,调用onChange又触发父组件状态更新,形成循环。

可行解决办法

方案1:用useCallback缓存回调函数

在父组件中,用useCallback缓存状态更新函数或绑定参数后的回调,确保函数引用稳定:

import { useCallback } from 'react';

// 缓存核心处理函数,改用函数式更新避免依赖外部data
const handleDamageChange = useCallback((panel, damageObject) => {
    setData(prevState => {
        const newDamage = {...prevState.damage};
        if (!damageObject) {
            delete newDamage[panel];
        } else {
            newDamage[panel] = damageObject;
        }
        return {...prevState, damage: newDamage};
    });
}, []); // 无外部依赖,函数引用永久稳定

// 缓存绑定bonnet的回调
const bonnetOnChange = useCallback((damageObject) => {
    handleDamageChange('bonnet', damageObject);
}, [handleDamageChange]);

// 父组件调用时传递缓存后的函数
<DamageInput onChange={bonnetOnChange} data={data.damage.bonnet} />

方案2:子组件用useRef保存最新的onChange

子组件中通过useRef存储最新的onChange,useEffect不再依赖onChange,而是通过ref.current调用:

import { useRef, useEffect, useState } from 'react';

export default function DamageInput({ data, onChange }){
    const onChangeRef = useRef(onChange);

    // 每次props更新时同步最新的onChange到ref
    useEffect(() => {
        onChangeRef.current = onChange;
    }, [onChange]);

    const [qty, setQty] = useState(data?.qty || null);
    const [selectedSize, setSelectedSize] = useState(data?.size || null);
    const [selectedSuppl, setSelectedSuppl] = useState(data?.suppl || []);

    useEffect(() => {
        // 从ref获取最新的onChange函数
        const currentOnChange = onChangeRef.current;
        if (!selectedSize) {
            currentOnChange(null);
        } else {
            const damageObject = {
                qty,
                size: selectedSize,
                suppl: selectedSuppl
            };
            currentOnChange(damageObject);
        }
    }, [selectedSize, qty, selectedSuppl]); // 仅依赖内部状态,不再依赖onChange

    ...
}

方案3:直接传递参数给子组件,避免匿名函数

修改子组件,新增panel props,让子组件直接调用父组件的无参数绑定函数:

// 子组件新增panel props
export default function DamageInput({ data, onChange, panel }){
    // ...内部状态不变

    useEffect(() => {
        if (!selectedSize) {
            onChange(panel, null);
        } else {
            const damageObject = { qty, size: selectedSize, suppl: selectedSuppl };
            onChange(panel, damageObject);
        }
    }, [selectedSize, qty, selectedSuppl, onChange, panel]);

    ...
}

// 父组件调用时,直接传递handleDamageChange和panel参数
<DamageInput onChange={handleDamageChange} panel="bonnet" data={data.damage.bonnet} />

同时父组件的handleDamageChange用useCallback缓存,确保引用稳定:

const handleDamageChange = useCallback((panel, damageObject) => {
    setData(prevState => {
        const newDamage = {...prevState.damage};
        if (!damageObject) {
            delete newDamage[panel];
        } else {
            newDamage[panel] = damageObject;
        }
        return {...prevState, damage: newDamage};
    });
}, []);

内容的提问来源于stack exchange,提问作者Daniel Kormos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 19:15:35