React如何匹配组件与状态?为何示例2中组件状态会重置?
React状态重置疑问:为何示例2中父组件变化会导致状态丢失?
针对示例1,官方文档提到:
对React而言,重要的是UI树中的位置——而非JSX标记!
App组件返回一个以
<Counter />作为第一个子元素的<div>。对React来说,这两个计数器拥有相同的“地址”:根节点的第一个子节点的第一个子节点。这就是React在前后渲染间匹配它们的方式,无论逻辑如何组织。
但在示例2中,为何父组件的变化(从<div>切换为<section>)会导致<Counter>组件的状态被重置?按照上面的说法,它不应该仍然是“根节点的第一个子节点的第一个子节点”这个地址吗?
示例1
import { useState } from 'react'; export default function App() { const [isFancy, setIsFancy] = useState(false); if (isFancy) { return ( <div> <Counter isFancy={true} /> <label> <input type="checkbox" checked={isFancy} onChange={e => { setIsFancy(e.target.checked) }} /> Use fancy styling </label> </div> ); } return ( <div> <Counter isFancy={false} /> <label> <input type="checkbox" checked={isFancy} onChange={e => { setIsFancy(e.target.checked) }} /> Use fancy styling </label> </div> ); }
示例2
export default function App() { const [isFancy, setIsFancy] = useState(false); return ( <div> {isFancy ? ( <div> <Counter isFancy={true} /> </div> ) : ( <section> <Counter isFancy={false} /> </section> )} <label> <input type="checkbox" checked={isFancy} onChange={e => { setIsFancy(e.target.checked) }} /> Use fancy styling </label> </div> ); }
解答
React匹配组件实例的规则,不止看子节点在父元素中的位置,还会对比父元素的类型:
- 示例1中,两次渲染时App返回的根元素始终是同一个
<div>,它的第一个子节点永远是<Counter>——所以React认为这是同一个组件实例,状态得以保留。 - 示例2中,根
<div>的第一个子节点在切换时,从<div>容器变成了<section>容器。React会把不同类型的父元素视为完全不同的UI分支,旧的<div>(包含内部的<Counter>)会被卸载,新的<section>(包含新的<Counter>)会被重新挂载。组件卸载时状态会被销毁,重新挂载自然就是全新的状态了。
如果要避免这种状态重置,可以给<Counter>添加唯一的key属性,让React通过key来识别这是同一个组件,而非依赖父元素类型和位置:
{isFancy ? ( <div> <Counter key="counter" isFancy={true} /> </div> ) : ( <section> <Counter key="counter" isFancy={false} /> </section> )}
内容的提问来源于stack exchange,提问作者MahoDev
相关产品推荐
相关产品推荐

