React筆記-首次渲染


渲染機制

渲染機制主要分為兩部分: 首次渲染和更新渲染。

首次渲染

首先通過一個小例子,來講解首次渲染過程。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
import React from 'react';
import ReactDOM from 'react-dom';

class ClickCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  handleClick = () => {
    this.setState((state) => {
      return {count: state.count + 1};
    });
  }
  render() {
    return [
      <button key="1" onClick={this.handleClick}>Update counter</button>,
      <span key="2">{this.state.count}</span>,
    ]
  }
}
ReactDOM.hydrate(<ClickCounter />, document.getElementById('root'));

程序運行到ReactDOM.hydrate時,其中的<ClickCounter />已被babel轉換為React.createElement(ClickCounter, null),生成的element如下:

{
    $$typeof: Symbol(react.element),
    key: null,
    props: {},
    ref: null,
    type: ClickCounter
}

接下來執行hydrate函數,生成root節點。首先了解下fiber的部分數據結構。

  • alternate(對應的workInProgressfiber
  • stateNode(關聯的fiber,組件實例或者DOM節點)
  • type(組件或HTML tag,如divspan等)
  • tag(類型,詳見workTags
  • effectTag(操作類型,詳見sideEffectTag
  • updateQueue(更新隊列)
  • memoizedState(state
  • memoizedProps(props
  • pendingProps(VDOM
  • return(父fiber
  • sibling(兄弟fiber
  • child(孩子fiber
  • firstEffect(第一個待處理的effect fiber
  • lastEffect(最后一個待處理的effect fiber

首次渲染會以同步渲染的方式進行渲染,首先創建一個update,將element裝載到其payload屬性中,然后合並到root.current.updateQueue,如果沒有updateQueue會創建一個。我們暫且將root.current看成HostRoot

接着根據HostRoot克隆一棵workInProgress更新樹。將HostRoot.alternate指向workInProgressworkInProgress.alternate指向HostRoot。然后進入workLoop進行更新樹操作部分。workLoop的任務也很簡單,就是將所有節點的更新掛載到更新樹上。下面詳細看看reconciliation階段。

reconciliation階段

reconciliation的核心在於workLoopworkLoop會以workInProgress為起點,即克隆的HostRoot,不斷向下尋找。如果workInProgress.child不為空,會進行diff;如果為空會創建workInProgress.child`。

// 第一次循環nextUnitOfWork為workInProgress
function workLoop(isYieldy) {
  if (!isYieldy) {
    // Flush work without yielding
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // Flush asynchronous work until there's a higher priority event
    while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

因為只涉及首次渲染,所以這里將performUnitOfWork簡單化。beginWork根據workInProgress.tag選擇不同的處理方式。先暫且看看如何處理HostRoot。進入updateHostRoot方法,先進行workInProgress.updateQueue的更新,計算新的state,將update.baseStateworkInProgress.memoizedState指向新的state。這里新的state裝載的是element

接下來調用createFiberFromElement創建fiber,將workInProgress.child指向該fiberfiber.return指向workInProgress

function performUnitOfWork(workInProgress) {
  let next = beginWork(workInProgress); // 創建workInProgress.child並返回
  if (next === null) { // 沒有孩子,收集effect list,返回兄弟或者父fiber
      next = completeUnitOfWork(workInProgress);
  }
  return next;
}

function beginWork(workInProgress) {
  switch(workInProgress.tag) {
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case ClassComponent: 
      ...
  }
}

用一張圖體現更新樹創建完成后的樣子:

workInProgress沒有孩子時,即創建的孩子為空。說明已經到達底部,開始收集effect

function completeUnitOfWork(workInProgress) {
  while (true) {
    let returnFiber = workInProgress.return;
    let siblingFiber = workInProgress.sibling;
    nextUnitOfWork = completeWork(workInProgress);
   ...// 省略收集effect list過程
    if (siblingFiber !== null) {
      // If there is a sibling, return it
      // to perform work for this sibling
      return siblingFiber;
    } else if (returnFiber !== null) {
      // If there's no more work in this returnFiber,
      // continue the loop to complete the parent.
      workInProgress = returnFiber;
      continue;
    } else {
      // We've reached the root.
      return null;
    }
  }
}
function completeWork(workInProgress) {
  //根據workInProgress.tag創建、更新或刪除dom
  switch(workInProgress.tag) {
    case HostComponent: 
      ...
  }
  return null;
}

協調算法過程結束后,workInProgress更新樹更新完畢,收集的effect list如下:

commit階段

effect list(鏈表)是reconciliation階段的結果,決定了哪些節點需要插入、更新和刪除,以及哪些組件需要調用生命周期函數。firstEffect記錄第一個更新操作,firstEffect.nextEffect(fiber)記錄下一個,然后繼續通過其nextEffect不斷往下尋找直至為null。下面是commit階段的主要流程:

// finishedWork為更新樹
function commitRoot(root, finishedWork) {
    commitBeforeMutationLifecycles();
    commitAllHostEffects();
    root.current = finishedWork;
    commitAllLifeCycles();
}

變量nextEffect每次執行完上面一個函數會被重置為finishedWork

  • commitBeforeMutationLifecycles

檢查effect list中每個fiber是否有Snapshot effect,如果有則執行getSnapshotBeforeUpdate

// 觸發getSnapshotBeforeUpdate
function commitBeforeMutationLifecycles() {
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;
    if (effectTag & Snapshot) {
      const current = nextEffect.alternate;
      commitBeforeMutationLifeCycles(current, nextEffect);
    }
    nextEffect = nextEffect.nextEffect;
  }
}
  • commitAllHostEffects

提交所有effect,實現dom的替換、更新和刪除。

function commitAllHostEffects() {
  while(nextEffect !== null) {
    var effectTag = nextEffect.effectTag;
    var primaryEffectTag = effectTag & (Placement | Update | Deletion);
    switch (primaryEffectTag) {
      case Placement: {
        commitPlacement(nextEffect);
        ...
      }
      case PlacementAndUpdate: {
        commitPlacement(nextEffect);
        var _current = nextEffect.alternate;
        commitWork(_current, nextEffect);
        ...
      }
      case Update: {
        var _current2 = nextEffect.alternate;
        commitWork(_current2, nextEffect); 
        ...
      }
      case Deletion: {// 觸發componentWillUnmout
        commitDeletion(nextEffect);
        ...
      }
    }
    nextEffect = nextEffect.nextEffect;
  }
}
  • commitAllLifeCycles

觸發componentDidMountcomponentDidUpdate

function commitAllLifeCycles(finishedRoot, committedExpirationTime) {
  while (nextEffect !== null) {
    var effectTag = nextEffect.effectTag;

    if (effectTag & (Update | Callback)) {
      var current$$1 = nextEffect.alternate;
      commitLifeCycles(finishedRoot, current$$1, nextEffect, committedExpirationTime);
    }
    if (effectTag & Ref) {
      commitAttachRef(nextEffect);
    }
    if (effectTag & Passive) {
      rootWithPendingPassiveEffects = finishedRoot;
    }

    nextEffect = nextEffect.nextEffect;
  }
}

總結

這里並未逐一細說,不想讀起來直犯困,更多講述了大概流程。如果覺得有疑惑的地方,也知道該在什么地方找到對應的源碼,解答疑惑。

更好的閱讀體驗在我的github,歡迎👏提issue。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM