React父组件重渲染时如何保留子组件状态?
问题描述
当catName或catWeight输入框更新时,父组件会触发重渲染,导致categories列表中的子组件<Category>也跟着重渲染,而且我尝试用React.memo()包装子组件却没有效果。现在想保留子组件的状态,应该选择保留子组件同时允许父组件重渲染,还是完全阻止父组件重渲染?具体该怎么操作?
const [catName, setcatName] = useState('') const [catWeight, setcatWeight] = useState('') const [categories, setCategories] = useState([]); let allGood = false; const handleSubmit = (e) => { e.preventDefault() if(catWeight <= 100) { allGood = true } if(catWeight < 1) { allGood = false } if(allGood) { setCategories([{ catName, catWeight }, ...categories]) } else alert("Something went wrong!") } ... return ( <div> <form onSubmit={handleSubmit}> <label> Enter Category Name: <input required autoCapitalize="words" autoComplete="off" type="text" name="cat-name" value={catName} onChange={ (e) => {setcatName(e.target.value) e.preventDefault()} } /> <br/> Enter Category Weight: <input required autoComplete="off" type="number" name="cat-name" value={catWeight} onChange={ (e) => setcatWeight(e.target.value) } /> </label> <input type="submit" value="Submit"/> </form> {/* displaying new category */} <div id="display-categories"> {categories.map(({ catName, catWeight }) => ( <Category key={uuid()} name={catName} weight={catWeight}/> ))} </div> </div> );
问题分析
你用React.memo没效果的核心原因是渲染时动态生成的key:每次父组件重渲染,uuid()都会生成全新的key值,React会判定这些key对应的是全新组件,直接销毁旧的<Category>实例并创建新的,导致子组件状态丢失,React.memo的浅比较完全起不到作用。
解决方案
方案一:保留子组件状态,允许父组件正常重渲染(推荐)
父组件因输入框更新重渲染是正常行为,我们只需要避免子组件被无意义地销毁重建,同时利用React.memo跳过不必要的重渲染。
1. 给每个category添加稳定唯一的ID
在添加新分类时就生成唯一ID,而不是在渲染时动态生成:
// 修改handleSubmit中的setCategories逻辑 if(allGood) { // 生成一次ID,后续不再变化 const newCategory = { id: uuid(), catName, catWeight } setCategories([newCategory, ...categories]) }
2. 渲染时使用稳定的key
{categories.map(({ id, catName, catWeight }) => ( <Category key={id} name={catName} weight={catWeight}/> ))}
3. 用React.memo包裹子组件
确保子组件接收的props是基础类型(字符串、数字),这样浅比较能生效:
const Category = React.memo(({ name, weight }) => { // 子组件内部状态会被保留,props不变时不会重渲染 return ( <div>{name} - {weight}</div> ) })
这样父组件重渲染时,只要子组件的name和weight没变化,React.memo就会跳过子组件的重渲染;同时因为key稳定,子组件不会被销毁,内部状态得以保留。
方案二:阻止父组件不必要的重渲染(可选)
如果想进一步减少父组件的重渲染次数,可以把输入表单抽成独立组件,让父组件只在categories变化时重渲染。
1. 拆分输入表单组件
把输入和提交逻辑单独封装,通过回调向父组件传递新分类:
const CategoryForm = ({ onAddCategory }) => { const [catName, setcatName] = useState('') const [catWeight, setcatWeight] = useState('') const handleSubmit = (e) => { e.preventDefault() let allGood = catWeight >= 1 && catWeight <= 100 if(allGood) { onAddCategory({ catName, catWeight }) // 提交后清空输入框 setcatName('') setcatWeight('') } else alert("Something went wrong!") } return ( <form onSubmit={handleSubmit}> <label> Enter Category Name: <input required autoCapitalize="words" autoComplete="off" type="text" name="cat-name" value={catName} onChange={(e) => setcatName(e.target.value)} /> <br/> Enter Category Weight: <input required autoComplete="off" type="number" name="cat-weight" value={catWeight} onChange={(e) => setcatWeight(e.target.value)} /> </label> <input type="submit" value="Submit"/> </form> ) }
2. 父组件只管理分类列表状态
const ParentComponent = () => { const [categories, setCategories] = useState([]); const handleAddCategory = (newCat) => { setCategories([{ id: uuid(), ...newCat }, ...categories]) } return ( <div> <CategoryForm onAddCategory={handleAddCategory}/> <div id="display-categories"> {categories.map(({ id, catName, catWeight }) => ( <Category key={id} name={catName} weight={catWeight}/> ))} </div> </div> ); }
这样输入框的状态变化只会触发CategoryForm的重渲染,父组件只有在categories更新时才会重渲染,进一步优化性能。
内容的提问来源于stack exchange,提问作者paehj12
相关产品推荐
相关产品推荐

