You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 17:57:06