在 React 的 render()(或函数组件的渲染路径)中
不应包含
-
副作用(Side effects): 如网络请求、订阅、定时器、I/O、路由跳转等。
- 为什么:render 应是纯函数,副作用会在每次渲染重复执行或引发循环。
- 替代:类组件放
componentDidMount/componentDidUpdate,函数组件放useEffect(并正确设置依赖)。
-
调用
setState或 导致状态变化的操作 : 直接或间接在 render 里调用setState会立即触发新渲染,容易导致无限循环。- 替代:把状态更新放到事件回调、生命周期、或 effect 中。
-
直接修改
this.state或可变数据(Mutation) : 如this.state.obj.x = 1、array.push()。- 为什么:会破坏不可变性,导致难以追踪的渲染问题。
- 替代:使用不可变拷贝:
const next = {...this.state.obj, x:1}或const nextArr = [...arr, item]。
-
长时间或昂贵的计算: CPU 密集型操作会阻塞渲染导致卡顿。
- 替代:使用
useMemo/memo/PureComponent或把计算异步化/提前计算。
- 替代:使用
-
创建新的函数/对象引用(频繁) : 在渲染每次都新建匿名函数或对象(如
onClick={() => doX()}、style={``{}})会使子组件收到不同引用,触发不必要重渲染。- 替代:用 class 方法、
useCallback、useMemo或提前提取常量。
- 替代:用 class 方法、
-
直接操作 DOM 或添加事件监听 : 如
document.addEventListener、手动修改节点。- 替代:使用 refs + 在
componentDidMount/useEffect中进行,并在卸载时清理。
- 替代:使用 refs + 在
-
异步/await 表达式 : render 不能是异步函数,返回值必须是 React 元素或
null。- 替代:在 effect 中做异步工作,状态准备好后渲染。
-
非确定性副作用(例如导航、弹窗、全局状态修改): 这些在每次渲染时重复执行会造成 UX/状态错乱。
- 替代:通过事件或 effect 控制一次性/受控触发。
-
抛出异常的操作(未捕捉): 如果 render 抛错会破坏整棵组件树(除非有 ErrorBoundary)。
- 替代:在渲染前验证数据,或在渲染外做可能失败的逻辑并保留安全值。
常见错误示例(错误)
jsx
// 错误:每次 render 都会创建定时器并在回调里 setState -> 无限循环
render() {
setTimeout(() => this.setState({ x: 1 }), 1000);
return <div>{this.state.x}</div>;
}
正确写法(类组件)
jsx
componentDidMount() {
this.timer = setTimeout(() => this.setState({ x: 1 }), 1000);
}
componentWillUnmount() {
clearTimeout(this.timer);
}
render() {
return <div>{this.state.x}</div>;
}
正确写法(函数组件 + Hook)
jsx
useEffect(() => {
const id = setTimeout(() => setX(1), 1000);
return () => clearTimeout(id);
}, []); // 空依赖:只在挂载时执行一次
(排查 render 问题时用)
- render 中有没有
setState/setTimeout/fetch/addEventListener?若有,把它们移动到 effect 或生命周期。 - 是否直接修改
state或 props 的对象/数组?若是,改为不可变更新。 - 是否每次渲染都创建了新的函数/对象作为 prop?考虑
useCallback/useMemo。 - 是否在组件卸载时清理了订阅或计时器?(
componentWillUnmount/ effect cleanup)