先记住这个答案
状态提升是把多个组件需要协调的状态移到合适的共同祖先,让它们读取同一份权威数据。Context 是把某个值提供给一片后代的传递机制,本身并不创建或保存状态。可以先在共同父层使用 useState 或 useReducer,再通过 props 或 Context 分发。选择时先讨论所有权和生命周期,再讨论传递是否冗长,不能把两者看成互相替代的状态库。
- 状态提升解决归属,Context 解决分发
- 共同所有者中的 state 可以通过 Context 提供
- 放置范围要覆盖消费者,也要符合状态应保留多久
两个库存面板怎样共享选中商品
假设左侧是商品列表,右侧是库存详情,两者必须围绕同一个 selectedSku 工作。若各自保存一份选中 ID,再靠 Effect 相互同步,就容易出现切换时不同步的问题。可以把选中 ID 放到包住两者的页面组件中,列表提交选择动作,详情读取同一份 ID。
到这一步,状态提升已经完成。页面可以直接向两侧传 props;如果详情区域有许多独立的深层消费者,也可以在这个页面范围内提供 Context。改成 Context 后,权威状态仍在页面组件中,并没有因为传递方式改变就自动成为全局数据。
生命周期比树的高度更值得先确认
如果切换商品页就应该清空选择,把状态留在该页面范围通常合理。如果用户切换多个页签仍要保留筛选条件,则需要把所有者放在切换期间仍保留的布局中,或设计明确的外部保存机制。Provider 的位置会影响可访问范围,其所在组件是否保留则决定内部状态是否延续。
Portal 改变 DOM 挂载位置,不会让所属 React 子树在所有者卸载后自动存活。弹层需要延续跨页面任务时,应设计持续存在的任务状态与挂载位置,而不是仅把弹层传送到 document.body。状态该重置还是保留,必须依据业务对象与流程判断。
避免为了方便把所有状态抬到根部
字段输入、展开面板和悬停等临时状态,若只有一个局部区域使用,通常留在当地更清楚。把这些值统一提升到应用根部,会扩大协调范围与维护负担;再用 Context 广播,也不会自动恢复原来的局部生命周期。
反过来,确实需要共享的状态也不应为了减少父层渲染而复制多份。可以保留单一所有者,通过组件组合、必要 props、独立 Context 或合适的订阅方案减少无关工作。状态设计先保证数据一致,再结合实际交互成本选择优化方式。
容易答错的地方
- 用了 Context 就不需要状态提升
- Context 本身只是提供一个值。多个组件要协调仍然需要明确的权威来源,Provider 所在的共同祖先持有 state,就是状态提升与 Context 同时使用的常见形式。
- Provider 放得高就会让全部后代订阅更新
- 只有读取相应 Context 的组件消费那条值变化;普通父层渲染可能带来额外工作,但不能把两种路径混为一谈。Provider 高度也不能单独证明性能好坏。
面试官还会怎么问?
只有两个兄弟组件共享数据需要 Context 吗?
通常先提升到共同父层并传 props 就足够。若两侧内部的多个独立消费者确实需要同一范围的数据,再考虑 Context,判断依据是依赖关系与可读性。
Context 可以提供不是 state 的值吗?
可以,例如静态配置、服务对象或 ref。只是这些对象内部变化不会自动产生 Context 更新;如果需要驱动展示,还要使用合适的状态或订阅机制。
共享状态放在最低共同祖先一定正确吗?
它是很好的起点,但还要看生命周期、复用范围和业务所有权。若这个祖先在切换时会卸载,而状态必须保留,就需要更持久的所有者或明确的保存恢复机制。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。