React中两个Counter组件为何处于UI树不同位置?
关于React条件渲染中组件位置的疑问解答
核心区别在于React对UI节点位置的判断逻辑
你觉得两个Counter处于同一个DOM位置,是从最终页面呈现的结果来看的,但React判断UI树里节点的位置,看的是JSX渲染逻辑里的占位分支,不是最终DOM的输出位置。
先看你贴的第一段代码的渲染逻辑:
import { useState } from 'react'; export default function Scoreboard() { const [isPlayerA, setIsPlayerA] = useState(true); return ( <div> {isPlayerA && <Counter person="Taylor" /> } {!isPlayerA && <Counter person="Sarah" /> } <button onClick={() => { setIsPlayerA(!isPlayerA); }}> Next player! </button> </div> ); } function Counter({ person }) { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{person}'s score: {score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
这里有两个完全独立的条件渲染块:
- 第一个块是
isPlayerA为真时,才会渲染Taylor的Counter - 第二个块是
!isPlayerA为真时,才会渲染Sarah的Counter
在React的UI树结构里,这是两个不同的节点位置:第一个条件块对应父组件子节点列表里的第1个可选位置,第二个条件块是第2个可选位置。当isPlayerA切换时,React会先卸载第一个位置的Counter,再在第二个位置挂载全新的Counter,所以组件状态(比如score)会被重置。
再看你预期的第二段代码:
import { useState } from 'react'; export default function Scoreboard() { const [isPlayerA, setIsPlayerA] = useState(true); return ( <div> {isPlayerA ? ( <Counter person="Taylor" /> ) : ( <Counter person="Sarah" /> )} <button onClick={() => { setIsPlayerA(!isPlayerA); }}> Next player! </button> </div> ); } function Counter({ person }) { const [score, setScore] = useState(0); const [hover, setHover] = useState(false); let className = 'counter'; if (hover) { className += ' hover'; } return ( <div className={className} onPointerEnter={() => setHover(true)} onPointerLeave={() => setHover(false)} > <h1>{person}'s score: {score}</h1> <button onClick={() => setScore(score + 1)}> Add one </button> </div> ); }
这里只有一个条件渲染块,属于父组件子节点列表里的同一个位置。当isPlayerA切换时,React会认为是同一个位置的组件在更新props,而不是卸载再挂载,所以Counter的状态会被保留。
关键规则:React识别相同节点的依据
React判断两个组件是否是同一个UI节点,会结合组件类型和在父组件中的渲染顺序位置:
- 第一段代码里的两个Counter,分别属于两个不同的条件分支,是父组件子节点列表里的两个独立项
- 第二段代码里的两个Counter,属于同一个条件分支,是父组件子节点列表里的同一个项
所以哪怕最终DOM看起来位置一样,React内部的UI树里,第一段代码的两个Counter是不同位置的节点,第二段是同一个位置的节点。
内容的提问来源于stack exchange,提问作者play_Er
相关产品推荐
相关产品推荐

