React 渲染 挂载阶段

83 阅读2分钟
export const ComponentA = props => {
  console.log('A render')
  
  const [count, setCount] = useState(0)

  // ref 回调,避免只拿到渲染前的null
  const commentListRefCB = useMemoizedFn(domRef => {
    console.log('commentListRefCBcommentListRefCBcommentListRefCB')
    if (domRef) {
      console.log('commentListRefCBcommentListRefCBcommentListRefCB进入 nnode不为空')
    }
  })
  
  useEffect(() => {
      console.log('进入A useEffect')
      setCount(2)
  }, [])
  
  useLayoutEffect(() => {
      console.log('进入A useLayoutEffect')
      setCount(1)
  })
  
  return(
      <div ref={commentListRefCB}>
          <span> {count} </span>
          <ComponentB>
      </div>
      )
  
}
export const ComponentB = props => {
  
  useEffect(() => {
      console.log('进入B useEffect')
  }, [])
  
  useLayoutEffect(() => {
      console.log('进入B useLayoutEffect')
  })
  
  return(
      <div>
          BBB
      </div>
      )
  
}

1. 渲染(Render)阶段

  • 定义:渲染是指React根据组件的状态(state)和属性(props)生成虚拟DOM(Virtual DOM)的过程。

  • 特点

    • 渲染是纯函数操作,不会直接操作真实的DOM。
    • 渲染阶段可能会被React暂停、中断或重新开始(尤其是在并发模式下)。
    • 在渲染阶段,React会调用组件的 render 方法(或函数组件的返回值)来生成虚拟DOM。
  • 触发时机

    • 组件首次挂载时。
    • 组件的 state 或 props 发生变化时。
    • 父组件重新渲染时(除非使用 React.memo 或 shouldComponentUpdate 进行优化)。

2. 挂载(Mount)阶段

  • 定义:挂载是指将渲染生成的虚拟DOM转换为真实DOM,并将其插入到浏览器的DOM树中的过程。

  • 特点

    • 挂载阶段会操作真实的DOM。
    • 在挂载阶段,React会调用生命周期方法或Hooks(如 useLayoutEffect 和 useEffect)。
    • 挂载阶段是同步的,React会确保在浏览器绘制之前完成挂载。
  • 触发时机

    • 组件首次渲染后。
    • 组件从DOM中移除后再次插入时(例如通过条件渲染)。

3. 渲染和挂载的关系

  • 渲染在前,挂载在后

    • React会先进行渲染,生成虚拟DOM。
    • 然后根据虚拟DOM的变化,进行挂载或更新操作。
  • 挂载是渲染的结果

    • 渲染阶段决定了虚拟DOM的结构。
    • 挂载阶段将虚拟DOM转换为真实DOM,并将其插入到页面中。

4. 代码中的渲染和挂载顺序

以下是你提供的代码中渲染和挂载的具体顺序:

4.1 渲染阶段

  1. ComponentA 渲染

    • 打印 ComponentA render
    • 生成 ComponentA 的虚拟DOM。
    • 发现 ComponentB 是 ComponentA 的子组件,开始渲染 ComponentB
  2. ComponentB 渲染

    • 生成 ComponentB 的虚拟DOM。

4.2 挂载阶段

  1. ComponentA 的 ref 回调

    • 在 ComponentA 的 div 元素挂载到DOM时,调用 commentListRefCB
    • 打印 commentListRefCBcommentListRefCBcommentListRefCB
    • 如果 domRef 不为 null,打印 commentListRefCBcommentListRefCBcommentListRefCB进入 nnode不为空
  2. useLayoutEffect 的执行:(React会先执行子组件的 useLayoutEffect,再执行父组件的 useLayoutEffect)

    • React会先执行子组件的 useLayoutEffect,再执行父组件的 useLayoutEffect
    • 打印 进入B useLayoutEffectComponentB 的 useLayoutEffect)。
    • 打印 进入A useLayoutEffectComponentA 的 useLayoutEffect)。
  3. 浏览器绘制

    • 浏览器将更新后的DOM绘制到屏幕上。
  4. useEffect 的执行:(React会先执行子组件的 useEffect,再执行父组件的 useEffect)

    • React会先执行子组件的 useEffect,再执行父组件的 useEffect
    • 打印 进入B useEffectComponentB 的 useEffect)。
    • 打印 进入A useEffectComponentA 的 useEffect)。