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

React中阻止子组件重复渲染求助:AcademyForm频繁重渲染

React子组件输入时重复渲染问题解决

问题背景

我有两个React.js文件:

  • AcademyRevision(父页面):作为顶层页面,内部嵌套子页面
  • AcademyForm(子页面):用于查看或编辑数据的表单页面,接收父组件传递的props

当前遇到的问题:在输入框中输入或修改值时,子页面总是重复渲染(通过console.log('AcademyForm '+title)可以确认)。

父组件(AcademyRevision)代码

import AcademyForm from "./components/academyForm";

export default function AcademyRevision(){
   // 省略其他逻辑
    return(
        <div className='page_wrap' style={{marginTop:60}}>
            <div className='row'>
                <div className='fixed-top page_wrap shadow-md' style={{backgroundColor: '#FFF', height:100}}>
                    <div className='pt-5'>
                        <PageHeader title='Academy Data' style={{fontSize : '1.25rem', fontWeight : '600'}} is_back_btn={true} customBack={true} backToPage={() => backToParent()}/>
                    </div>
                </div>
                <div className="col-12 mb-5">
                    <AcademyForm
                        title='Academy 1'
                        disabled={disableButton}
                        ipt_academy={ipt_academy1}
                        handleAcademy={handleAcademy1}
                        masterAcademyLevel={masterAcademyLevel}
                        ipt_school={ipt_school1}
                        set_ipt_school={(event, value) => set_ipt_school1(value)}
                        ipt_major={ipt_major1}
                        handleMajor={handleMajor1}
                        masterMajor={masterMajor}
                        ipt_start={ipt_start1}
                        set_ipt_start={(newValue) => set_ipt_start1(newValue)}
                        ipt_finish={ipt_finish1}
                        set_ipt_finish={(newValue) => set_ipt_finish1(newValue)}
                        ipt_description={ipt_description1}
                        set_ipt_description={(e) => set_ipt_description1(e.target.value)}
                        ipt_score={ipt_score1}
                        set_ipt_score={(e) => set_ipt_score1(e.target.value)}
                    />
                </div>
            </div>
        </div>
    )
}

子组件(AcademyForm)代码

import { memo } from 'react';
// 省略其他导入(如Material-UI组件、ViewField等)

function AcademyForm({
    title,
    disabled,
    ipt_academy,
    handleAcademy,
    masterAcademyLevel,
    ipt_school,
    set_ipt_school,
    ipt_major,
    handleMajor,
    masterMajor,
    ipt_start,
    set_ipt_start,
    ipt_finish,
    set_ipt_finish,
    ipt_description,
    set_ipt_description,
    ipt_score,
    set_ipt_score
}){
    return(
        <div className={'custom-tabs-bg-solusiqu p-3 mb-3'}>
        {console.log('AcademyForm '+title)}
            <h5>{title}</h5>
            <div className="form-group row">
                <div className="col row">
                    <label className='custom-input-solusiqu col-6 col-form-label'>Academy</label>
                </div>
                <div className="col-12">
                    <FormControl 
                        required
                        disabled={disabled}
                        sx={{ width:'100%',
                            "& .MuiInputBase-input": {
                                fontSize: '0.9rem',
                            }
                        }}
                    >
                        <Select
                            size='small'
                            value={ipt_academy}
                            onChange={handleAcademy}
                        >
                            {
                            masterAcademyLevel?.map((data,index)=>(
                                <MenuItem key={index} value={data.CIDEDUCATION}>{data.CEDUCATION}</MenuItem>
                            ))
                            }
                        </Select>
                    </FormControl>
                </div>
            </div>
            <div className='form-group row'>
                <div className="col row">
                    <label className='custom-input-solusiqu col-6 col-form-label'>School/Campus</label>
                </div>
                <div className="col-12">
                    <Autocomplete
                        onChange={set_ipt_school}
                        value={ipt_school}          
                        disabled={disabled}                 
                        options={masterSchool}
                        size='small'
                        getOptionLabel={(option) => option.CSCHOOL ? option.CSCHOOL: ""}
                        isOptionEqualToValue={(option, value) => option.CIDSCHOOL===value.CIDSCHOOL||value===''}                        
                        renderOption={(props, option) => {
                            return (
                              <li {...props} key={option.CIDSCHOOL}>
                                {option.CSCHOOL}
                              </li>
                            );
                        }}
                        renderInput={(params) => <TextField {...params} />}
                        sx={{ width:'100%',
                            "& .MuiInputBase-input": {
                                fontSize: '0.9rem',
                                textTransform:'uppercase',
                            }
                        }}
                    />
                </div>
            </div>
            <div className="form-group row">
                <div className="col row">
                    <label className='custom-input-solusiqu col-6 col-form-label'>Major</label>
                </div>
                <div className="col-12">
                    <FormControl 
                        required
                        disabled={disabled}
                        sx={{ width:'100%',
                            "& .MuiInputBase-input": {
                                fontSize: '0.9rem'
                            }
                        }}
                    >
                        <Select
                            size='small'
                            value={ipt_major}
                            onChange={handleMajor}
                        >
                            {
                            masterMajor?.map((data,index)=>(
                                <MenuItem key={index} value={data.CIDMAJOR}>{data.CMAJOR}</MenuItem>
                            ))
                            }
                        </Select>
                    </FormControl>
                </div>
            </div>
            <div className="form-group row pt-4">
                <div className="col-6">
                    <LocalizationProvider dateAdapter={AdapterDayjs}>
                        <MobileDatePicker
                            value={ipt_start}
                            disabled={disabled}
                            label='Start'
                            size='small'
                            views={['year', 'month', 'day']}
                            onChange={set_ipt_start}
                            sx={{width: '100%',
                                "& .MuiInputBase-input": {
                                    height: '0.35em',
                                    fontSize: '0.9rem'
                                }
                            }}
                        />
                    </LocalizationProvider>
                </div>
                <div className="col-6">
                    <LocalizationProvider dateAdapter={AdapterDayjs}>
                        <MobileDatePicker
                            value={ipt_finish}
                            disabled={disabled}
                            label='Finish'
                            size='small'
                            views={['year', 'month', 'day']}
                            onChange={set_ipt_finish}
                            sx={{width: '100%',
                                "& .MuiInputBase-input": {
                                    height: '0.35em',
                                    fontSize: '0.9rem'
                                }
                            }}
                        />
                    </LocalizationProvider>
                </div>
            </div>
            <ViewField
                data={ipt_description}
                disabled={disabled}
                inputClass='text-uppercase'
                type='text'
                useLabel={1}
                label='Description'
                onChange={set_ipt_description}
                style={{fontSize:'0.9rem'}}
            />
            <ViewField
                data={ipt_score}
                disabled={disabled}
                type='number'
                useLabel={1}
                label='Score'
                onChange={set_ipt_score}
                style={{fontSize:'0.9rem'}}
            />
        </div>
    )
}

export default memo(AcademyForm)

问题原因

虽然子组件已经用memo包裹(通过浅比较props避免不必要渲染),但父组件传递的props中存在内联函数,比如set_ipt_school={(event, value) => set_ipt_school1(value)}。每次父组件状态更新(比如输入框值变化触发setState)时,这些内联函数会被重新创建,生成新的引用。memo会判定props发生变化,从而触发子组件重复渲染。

另外,如果masterAcademyLevel、masterMajor这类数组/对象类型的props是每次父组件渲染时重新生成的(比如直接从接口返回未缓存),它们的引用变化也会触发子组件渲染。

解决方案

1. 用useCallback稳定函数引用

在父组件中,将所有内联回调函数用useCallback包裹,确保每次父组件渲染时函数引用不变:

import { useCallback } from 'react';
import AcademyForm from "./components/academyForm";

export default function AcademyRevision(){
   // 省略其他逻辑

   // 用useCallback包裹回调函数
   const handleSetSchool = useCallback((event, value) => {
     set_ipt_school1(value);
   }, [set_ipt_school1]);

   const handleSetStart = useCallback((newValue) => {
     set_ipt_start1(newValue);
   }, [set_ipt_start1]);

   const handleSetFinish = useCallback((newValue) => {
     set_ipt_finish1(newValue);
   }, [set_ipt_finish1]);

   const handleSetDescription = useCallback((e) => {
     set_ipt_description1(e.target.value);
   }, [set_ipt_description1]);

   const handleSetScore = useCallback((e) => {
     set_ipt_score1(e.target.value);
   }, [set_ipt_score1]);

    return(
        <div className='page_wrap' style={{marginTop:60}}>
            {/* 省略其他结构 */}
            <div className="col-12 mb-5">
                <AcademyForm
                    title='Academy 1'
                    disabled={disableButton}
                    ipt_academy={ipt_academy1}
                    handleAcademy={handleAcademy1}
                    masterAcademyLevel={masterAcademyLevel}
                    ipt_school={ipt_school1}
                    set_ipt_school={handleSetSchool} // 使用稳定的函数引用
                    ipt_major={ipt_major1}
                    handleMajor={handleMajor1}
                    masterMajor={masterMajor}
                    ipt_start={ipt_start1}
                    set_ipt_start={handleSetStart}
                    ipt_finish={ipt_finish1}
                    set_ipt_finish={handleSetFinish}
                    ipt_description={ipt_description1}
                    set_ipt_description={handleSetDescription}
                    ipt_score={ipt_score1}
                    set_ipt_score={handleSetScore}
                />
            </div>
        </div>
    )
}

2. 用useMemo稳定数组/对象引用

如果masterAcademyLevel、masterMajor是动态生成的数组/对象,用useMemo缓存它们,避免每次父组件渲染时引用变化:

import { useCallback, useMemo } from 'react';

export default function AcademyRevision(){
   // 省略其他逻辑

   // 缓存数组/对象,确保引用稳定
   const memoizedAcademyLevel = useMemo(() => masterAcademyLevel, [masterAcademyLevel]);
   const memoizedMasterMajor = useMemo(() => masterMajor, [masterMajor]);

    return(
        <div className='page_wrap' style={{marginTop:60}}>
            {/* 省略其他结构 */}
            <div className="col-12 mb-5">
                <AcademyForm
                    // 省略其他props
                    masterAcademyLevel={memoizedAcademyLevel}
                    // 省略其他props
                    masterMajor={memoizedMasterMajor}
                    // 省略其他props
                />
            </div>
        </div>
    )
}

原理说明

memo对props进行浅比较:只有当props的引用或值(基础类型)发生变化时,才会触发子组件重新渲染。通过useCallback稳定函数引用、useMemo稳定对象/数组引用,确保父组件状态更新时,子组件接收的props不会出现不必要的引用变化,从而避免重复渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 13:22:01