- Published on
- 约 1893 字
吃透 React 组件复用与状态保留:从一个顺序调整案例说起
- Authors

- Name
- 小辉辉
在 React 开发中,“组件是否重建”“状态是否保留” 是高频踩坑点,尤其当组件结构发生变化时,很多开发者会困惑于 React 内部的判断逻辑。本文将通过两个相似但结论不同的组件结构调整案例,拆解 React 组件复用的核心规则,帮你彻底理清组件实例与状态的保留机制。
案例背景:两组相似的结构调整
先明确基础组件结构,所有案例的根组件均为 <A>,子组件为 <B2>(固定类型),<B2> 内部嵌套 <C> 或 <C1> 组件(均为自定义函数 / 类组件,含内部 state,如输入框内容)。
我们将对比两组结构调整场景,分析其背后的差异:
场景 1:父组件类型变更(B2 → B3)
原结构:
<A>
<B2 key="1"><C/></B2> // B2 实例1,内部嵌套 C
<B2 key="2"><C1/></B2> // B2 实例2,内部嵌套 C1
</A>
调整后结构:
<A>
<B3 key="1"><C/></B3> // 父组件类型从 B2 变为 B3
<B3 key="2"><C1/></B3> // 父组件类型从 B2 变为 B3
</A>
关键结论:C 和 C1 全重建,state 重置
React 判断组件是否复用的第一优先级是 “组件类型”:
- 父组件从
<B2>变为<B3>,类型完全不同,React 会认为这是 “全新组件”,直接卸载原有的<B2>实例 1 和实例 2。 - 子组件
<C>和<C1>依赖父组件存在,父组件卸载时,子组件会被 “连带卸载”,其内部 state(如输入框内容)会被销毁。 - 新创建的
<B3>会重新渲染内部的<C>和<C1>,此时的<C>和<C1>是全新实例,state 会重新初始化(回到初始值)。
这里需要注意:key 值(1 和 2)未变,但 key 仅作用于 “同类型组件” 的复用判断,跨类型场景下 key 无效。
场景 2:父组件顺序调整(key 位置交换)
原结构:
<A>
<B2 key="1"><C/></B2> // B2 实例1(key=1),内部 C
<B2 key="2"><C1/></B2> // B2 实例2(key=2),内部 C1
</A>
调整后结构:
<A>
<B2 key="2"><C1/></B2> // B2 实例2(key=2),内部 C1(位置前移)
<B2 key="1"><C/></B2> // B2 实例1(key=1),内部 C(位置后移)
</A>
关键结论:C 和 C1 不重建,state 完全保留
此场景中,组件类型未变,仅顺序调整,React 的判断逻辑完全不同:
- 父组件始终是
<B2>(类型未变),且 key 值 “1” 和 “2” 依然存在(仅交换位置)。React 会通过 key 识别 “这是原来的两个 B2 实例”,直接复用,不执行 “卸载→重建”。 - 子组件
<C>始终属于 key=1 的<B2>,<C1>始终属于 key=2 的<B2>(子组件类型未变)。因此,<C>和<C1>会被复用,内部 state 完整保留(如输入框内容不会消失)。 - 最终变化仅停留在 DOM 渲染顺序:页面上 key=2 的
<B2>(含 C1)会显示在 key=1 的<B2>(含 C)之前,但组件实例和状态未发生任何改变。
提炼核心规则:React 组件复用的 3 个判断维度
通过两个案例的对比,我们可以总结出 React 决定 “组件是否重建”“state 是否保留” 的核心规则,按优先级排序如下:
1. 组件类型(最高优先级)
- 同类型组件:才有可能被复用(后续看 key 和子组件)。
- 不同类型组件:直接判定为 “全新组件”,原组件卸载(含子组件),新组件重建(子组件也重建),state 重置。
- 例:B2 → B3 属于不同类型,直接触发重建;B2 → B2 属于同类型,进入下一步判断。
2. key 值(同类型组件的唯一标识)
- key 是 React 识别 “同类型组件实例” 的唯一依据,key 不变则实例不变。
- 即使组件顺序调整,只要 key 存在且对应实例未被删除,组件就会被复用(而非重建)。
- 例:场景 2 中 key=1 和 key=2 的 B2 实例,仅交换顺序但 key 未变,因此被复用。
3. 子组件结构(父组件复用后的补充判断)
- 若父组件被复用,且子组件类型未变:子组件会被复用,state 保留。
- 若父组件被复用,但子组件类型改变(如 C → C1):子组件会被卸载重建,state 重置。
- 例:若场景 2 中 key=1 的 B2 内部从
<C>变为<C1>,则 C 会卸载、C1 会重建,state 重置。
实践建议:避免状态丢失的 2 个关键技巧
在实际开发中(如列表渲染、条件渲染),掌握上述规则可以帮你避免不必要的状态丢失,提升性能和用户体验:
1. 列表渲染必须加 key,且 key 不能随意变化
- 避免用 index 作为 key(若列表增删改查,index 可能变化,导致 key 对应实例错乱,触发不必要的重建)。
- 优先用唯一且稳定的标识作为 key(如接口返回的 id),确保组件实例与数据一一对应,state 稳定。
2. 条件渲染时,尽量保持组件类型一致
- 若需要切换不同内容(如 “编辑态” 和 “查看态”),优先通过 props 控制组件内部显示(如
isEdit={true}),而非切换组件类型(如 EditComponent → ViewComponent)。 - 例:用
<B2 isEdit={true}>代替<EditB2>和<ViewB2>,避免因组件类型变化导致 state 丢失。
代码验证:可运行的 Demo 示例
为了让你更直观地感受 “顺序调整不影响 state”,这里提供一个可运行的 React 代码示例。你可以复制到本地项目中,操作输入框后调整顺序,观察 state 是否保留:
import { useState } from 'react';
// 子组件 C:含输入框 state
const C = () => {
const [value, setValue] = useState('');
return (
<div style={{ margin: '10px 0' }}>
<span>C 组件输入框:</span>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="输入内容..."
/>
</div>
);
};
// 子组件 C1:含输入框 state
const C1 = () => {
const [value, setValue] = useState('');
return (
<div style={{ margin: '10px 0' }}>
<span>C1 组件输入框:</span>
<input
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="输入内容..."
/>
</div>
);
};
// 父组件 B2
const B2 = ({ children, key }) => {
return (
<div style={{ padding: '10px', border: '1px solid #ccc' }}>
B2 组件(key: {key})
{children}
</div>
);
};
// 根组件 A
const A = () => {
const [order, setOrder] = useState('original'); // 控制顺序
// 切换顺序:原顺序 / 交换顺序
const toggleOrder = () => {
setOrder(order === 'original' ? 'swapped' : 'original');
};
return (
<div style={{ padding: '20px' }}>
<h3>React 组件顺序调整 Demo</h3>
<button onClick={toggleOrder} style={{ marginBottom: '20px' }}>
切换 B2 组件顺序
</button>
<div>
{order === 'original' ? (
<>
<B2 key="1"><C/></B2>
<B2 key="2"><C1/></B2>
</>
) : (
<>
<B2 key="2"><C1/></B2>
<B2 key="1"><C/></B2>
</>
)}
</div>
</div>
);
};
export default A;
运行后,在输入框中输入内容,点击 “切换 B2 组件顺序”,你会发现:输入框内容(即 C 和 C1 的 state)完全保留,仅组件显示顺序发生变化。
总结
React 组件的复用与状态保留,本质是 “按规则识别实例” 的过程:
- 组件类型是 “第一道门槛”,类型不同则实例必换;
- key 是 “同类型组件的身份证”,key 不变则实例不变;
- 子组件的命运依附于父组件,父组件复用且子组件类型不变,则子组件复用、state 保留。
理解这些规则,能帮你在开发中精准控制组件状态,避免 “莫名其妙的 state 丢失”,同时减少不必要的组件重建,提升应用性能。
