Yasin

Yasin

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 阶段完成
}

此阶段:

  • ✅ 可以读写 statememocontext
  • ✅ 可以持有 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 ✅ 有值 ✅ 推荐