第1章 状态提升的核心原则

状态提升(State Hoisting)是将组件内部状态迁移到共同祖先组件中,通过 props 向下分发的模式。其复用性提升的根基在于三条核心原则:单向数据流、状态所有权与组件职责分离。本章阐述三者的定义、边界划分以及它们如何共同构建可复用组件体系。

1.1 单向数据流

单向数据流规定数据只能从父组件向子组件传递,子组件不直接修改父组件的状态。这种约束使得数据变更路径清晰可追踪,避免了双向绑定带来的调试困难。

  • 数据源唯一:每次状态变更必然来自拥有该状态的组件。
  • 变更可预测:数据流动方向固定,易于定位 bug。
  • 子组件无副作用:子组件仅通过回调函数向父组件传递意图,不越权修改。

1.2 状态所有权

状态所有权明确了“哪个组件拥有该状态”。提升后的状态应归属于共同祖先,该祖先负责状态的创建、更新与销毁。所有子组件均为“只读消费者”。

所有权规则:若多个同级组件需要共享同一状态,则该状态必须上移至其最近的共同祖先。这一定位标准是状态提升行为的关键决策点。

场景所有权归属复用影响
只被单个组件使用留在该组件内部无提升必要
被兄弟组件共享上移至父组件提升后子组件变为纯展示,可复用
被跨层级组件共享上移至更高级祖先或全局需谨慎权衡,避免过度提升

1.3 组件职责分离

职责分离要求每个组件只承担一件事。状态提升后,父组件成为“智能容器”(管理状态与逻辑),子组件变为“哑组件”(仅根据 props 渲染 UI)。这种分离使得子组件脱离具体业务状态,方便在不同父组件中复用。

  • 容器组件:负责状态生命周期、请求、事件处理。
  • 展示组件:无内部状态,仅通过 props 和回调与容器交互。
  • 边界清晰:展示组件不应引入任何状态管理依赖。

实践建议:先写出展示组件的纯渲染逻辑,再考虑需要哪些状态和回调,最后将状态提升到合适的容器组件中。自底向上的推导方式更容易保持职责分离。

原则协同:一个示例

以下代码展示了一个简单的计数器对:两个按钮组件共同展示同一计数,状态由父组件 CounterPair 持有,子组件 CountButton 通过 props 接收 value 和 onIncrement。

// 父组件:状态所有者
function CounterPair() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <CountButton value={count} onIncrement={() => setCount(c => c + 1)} />
      <CountButton value={count} onIncrement={() => setCount(c => c + 1)} />
    </div>
  );
}

// 子组件:纯展示,可供不同场景复用
function CountButton({ value, onIncrement }) {
  return <button onClick={onIncrement}>{value}</button>;
}

该示例体现了三个原则:单向数据流(父→子传递 count)、状态所有权(父拥有 count)、职责分离(CountButton 不管理状态)。

状态提升结构图 父组件(状态所有者) count 状态持有者 子组件 A 只读 props.value 子组件 B 只读 props.value props.value props.value 单向数据流 仅父→子传递 职责分离 容器 vs 展示 状态所有权:父组件唯一

重构落地步骤(预览)

在后续章节中,我们将展开完整的重构流程。第1章仅提供核心原则,后续将围绕以下步骤组织实践:

  1. 识别共享状态
  2. 确定共同祖先
  3. 提取状态至祖先
  4. 通过 props 传递状态与回调
  5. 验证单向数据流是否被破坏

验收标准

完成本章学习后,你应能:

  • 用一句话复述单向数据流的含义。
  • 说明状态所有权的决策依据(组件层级关系)。
  • 区分容器组件与展示组件的职责边界。
  • 在代码评审中识别出违背三大原则的模式。