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
相关产品推荐
相关产品推荐

