保持组件纯粹
渲染函数为什么需要保持纯粹?
- 纯度: 对相同 props、state 和 context 给出相同结果,且不修改渲染前已存在的外部数据;
- 调度原因: React 可能重复执行、暂停或放弃一次渲染;只有计算与外部变化分开,重试才不会重复扣款、订阅或改写页面;
- 非幂等读取: 在渲染中直接依赖
Date.now()或Math.random()会使相同输入产生不同结果,应把变化建模为状态或在合适事件中产生;
为什么可以修改本次渲染创建的局部值?
- 局部修改: 新建数组后 push 元素不会影响之前的渲染,前提是该对象没有在本次渲染前被外部持有;
- 边界判断: 关键不是是否出现赋值,而是有没有修改已经存在、被其他代码共享的值;
function Cups({ count }) {
const cups = [];
for (let i = 0; i < count; i++) {
cups.push(<li key={i}>杯子 {i + 1}</li>);
}
return <ul>{cups}</ul>;
}
如何保持输入与 JSX 使用的值不可变?
- Props 和 state: 把已有值当作快照,不修改对象字段;对象与数组更新方法分别见交互章节;
- Hook 输入输出: 不通过修改参数或返回对象绕过 Hook 的计算契约;ref.current 的专门可变用途见 ref 笔记;
- JSX 快照: 对象一旦被用于 JSX 就不要再改它,先完成局部计算,再创建使用该值的 JSX;
const firstStyle = { color: "red" };
const first = <p style={firstStyle}>第一段</p>;
const secondStyle = { ...firstStyle, color: "blue" };
const second = <p style={secondStyle}>第二段</p>;
外部变化应该由哪里执行?
- 交互原因: 点击、提交等具体操作产生的副作用放在事件处理器中;
- 同步原因: UI 出现或输入变化后需要与外部系统保持一致时使用 Effect,具体判断见区分事件与 Effect;
- 检查工具: StrictMode 的额外调用用于暴露不纯行为,其完整检查范围归属内置组件笔记;