react的执行阶段
React 的执行阶段
1. Render 阶段(纯计算,无副作用)
React 调用你的函数组件本体:
function AllInOneAgentPage() {
// ← 从这里开始,全部是 Render 阶段
const chatRef = useRef(null); // 创建/复用 ref 容器
const [tools, setTools] = useState(null); // 读取当前 state
const baseTools = useMemo(() => [...], []); // 执行 memo 计算
useProvideAgentTools(baseTools); // 执行 hook 内部逻辑
return <WorldClassChatContainer ... />; // 返回 React 元素(虚拟 DOM)
// ← 到这里结束,Render 阶段完成
}
此阶段:
- ✅ 可以读写
state、memo、context - ✅ 可以持有
chatRef(ref 对象本身) - ❌ 不能读
chatRef.current(子组件还没挂载,值是null) - ❌ 不执行
useEffect的回调 - ⚠️ 可能被 React 执行多次(并发模式下)
2. Commit 阶段(操作真实 DOM)
React 拿到虚拟 DOM,分三个子阶段提交:
BeforeMutation → Mutation(真正操作 DOM) → Layout
Mutation 子阶段:
- React 把
<WorldClassChatContainer ref={chatRef} />挂载到真实 DOM - 执行
useImperativeHandle,把暴露对象写入chatRef.current - 此时
chatRef.current才有值!
Layout 子阶段:
- 执行
useLayoutEffect的回调(同步,阻塞浏览器绘制)
3. 浏览器绘制
浏览器把新的 DOM 画到屏幕上。
4. useEffect 执行(异步,绘制之后)
useEffect(() => {
// ← 在这里,chatRef.current 已经是完整的 ImperativeHandle 对象
setRefDependentTools({
htmlPreviewFromFile: createHtmlPreviewFromFileTool(
(key, config, props) => chatRef.current?.openCustomPanel(...) // ✅ 安全
)
});
}, []);
完整时序图
┌─────────────────────────────────────────────────────────────┐
│ React 调用组件函数 │
│ • useRef / useState / useMemo / return JSX │
│ chatRef.current === null │
└──────────────────────┬──────────────────────────────────────┘
│ React 计算出 diff
┌──────────────────────▼──────────────────────────────────────┐
│ Commit — Mutation │
│ • 真实 DOM 插入/更新 │
│ • useImperativeHandle 执行 │
│ chatRef.current = { openCustomPanel, iframeManager, ... } │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ Commit — Layout │
│ • useLayoutEffect 回调(同步) │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ 浏览器绘制画面 │
└──────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────▼──────────────────────────────────────┐
│ useEffect 回调(异步) │
│ • setRefDependentTools(...) │
│ • 触发一次新的 Render → Commit(带上工具的第二次渲染) │
└─────────────────────────────────────────────────────────────┘
为什么工具必须在 useEffect 里创建
| 时机 | chatRef.current |
能否创建闭包访问它 |
|---|---|---|
| Render 阶段 | null |
❌ React Compiler 禁止 |
useLayoutEffect |
✅ 有值 | ✅ 可以,但会阻塞绘制 |
useEffect |
✅ 有值 | ✅ 推荐 |
