欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React18系列reconciler從0實(shí)現(xiàn)過程詳解

 更新時(shí)間:2023年01月16日 15:39:38   作者:sunnyhuang519626  
這篇文章主要介紹了React18系列reconciler從0實(shí)現(xiàn)過程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

本系列是講述從0開始實(shí)現(xiàn)一個(gè)react18的基本版本。由于React源碼通過Mono-repo 管理倉庫,我們也是用pnpm提供的workspaces來管理我們的代碼倉庫,打包我們使用rollup進(jìn)行打包。

倉庫地址

我們這一次主要寫有關(guān)調(diào)和(reconciler)和ReactDom,React將調(diào)和單獨(dú)的抽出一個(gè)包,暴露出入口,通過不同的宿主環(huán)境去調(diào)用不同的api。

React-Dom包

這個(gè)包主要是提供瀏覽器環(huán)境的一些dom操作。主要是提供2個(gè)文件hostConfig.ts 以及root.ts。 想想我們?cè)赗eact18中,是通過如下方式調(diào)用的。所以我們需要提供一個(gè)方法createRoot方法,返回要給包含render函數(shù)的對(duì)象。

import ReactDOM from 'react-dom/client';
ReactDOM.createRoot(root).render(<App />)

createRoot

主要功能是2個(gè),第一個(gè)是創(chuàng)建根fiberNode節(jié)點(diǎn), 第二個(gè)創(chuàng)建更新(初始化主要是用于渲染),開始調(diào)度。

//createRoot.ts 文件
import {
  createContainer,
  updateContainer,
} from "../../react-reconciler/src/filerReconciler";
export function createRoot(container: Container) {
  const root = createContainer(container);
  return {
    render(element: ReactElementType) {
      updateContainer(element, root);
    },
  };
}

createRoot.js主要是調(diào)用的react-reconcilercreateContainer方法和updateContainer方法。我們之后看看這2個(gè)方法主要的作用

hostConfig.ts

主要是創(chuàng)建各種dom,已經(jīng)dom的插入操作

export const createInstance = (type: string, props: any): Instance => {
  // TODO 處理props
  const element = document.createElement(type);
  return element;
};
export const appendInitialChild = (
  parent: Instance | Container,
  child: Instance
) => {
  parent.appendChild(child);
};
export const createTextInstance = (content: string) => {
  return document.createTextNode(content);
};
export const appendChildToContainer = appendInitialChild;

React-reconciler包

createContainer() 函數(shù)

從上面我們可以知道,首先調(diào)用的createContainerupdateContainer,我們把它寫到filerReconciler.tscreateContainer接受傳入的dom元素。

/**
 * ReactDOM.createRoot()中調(diào)用
 * 1. 創(chuàng)建fiberRootNode 和 hostRootFiber。并建立聯(lián)系
 * @param {Container} container
 */
export function createContainer(container: Container) {
  const hostRootFiber = new FiberNode(HostRoot, {}, null);
  const fiberRootNode = new FiberRootNode(container, hostRootFiber);
  hostRootFiber.updateQueue = createUpdateQueue();
  return fiberRootNode;
}

可以看到我們?cè)谶@里主要就是2個(gè)事情

調(diào)用了2個(gè)方法去創(chuàng)建2個(gè)不同的fiberNode,一個(gè)是hostRootFiber,一個(gè)是fiberRootNode

創(chuàng)建一個(gè)更新隊(duì)列,并將其賦值給hostRootFiber

/**
 * 頂部節(jié)點(diǎn)
 */
export class FiberRootNode {
  container: Container; // 不同環(huán)境的不同的節(jié)點(diǎn) 在瀏覽器環(huán)境 就是 root節(jié)點(diǎn)
  current: FiberNode;
  finishedWork: FiberNode | null; // 遞歸完成后的hostRootFiber
  constructor(container: Container, hostRootFiber: FiberNode) {
    this.container = container;
    this.current = hostRootFiber;
    hostRootFiber.stateNode = this;
    this.finishedWork = null;
  }
}
export class FiberNode {
  constructor(tag: WorkTag, pendingProps: Props, key: Key) {
    this.tag = tag;
    this.pendingProps = pendingProps;
    this.key = key;
    this.stateNode = null; // dom引用
    this.type = null; // 組件本身  FunctionComponent () => {}
    // 樹狀結(jié)構(gòu)
    this.return = null; // 指向父fiberNode
    this.sibling = null; // 兄弟節(jié)點(diǎn)
    this.child = null; // 子節(jié)點(diǎn)
    this.index = 0; // 兄弟節(jié)點(diǎn)的索引
    this.ref = null;
    // 工作單元
    this.pendingProps = pendingProps; // 等待更新的屬性
    this.memoizedProps = null; // 正在工作的屬性
    this.memoizedState = null;
    this.updateQueue = null;
    this.alternate = null; // 雙緩存樹指向(workInProgress 和 current切換)
    this.flags = NoFlags; // 副作用標(biāo)識(shí)
    this.subtreeFlags = NoFlags; // 子樹中的副作用
  }
}

接下來,我們看看createUpdateQueue里面的執(zhí)行邏輯。執(zhí)行了一個(gè)函數(shù),返回了一個(gè)對(duì)象。所以現(xiàn)在hostRootFiberupdateQueue指向了這個(gè)指針

/**
 * 初始化updateQueue
 * @returns {UpdateQueue<Action>}
 */
export const createUpdateQueue = <State>() => {
  return {
    shared: {
      pending: null,
    },
  } as UpdateQueue<State>;
};

我們從上面createRoot執(zhí)行完后,返回了一個(gè)render函數(shù),我們接下來看看render后的執(zhí)行過程,是怎么渲染到頁面的。

render() 調(diào)用

createRoot執(zhí)行后,創(chuàng)建了一個(gè)rootFiberNode, 并返回了render調(diào)用,主要是執(zhí)行了updateContainer用于去渲染初始化的工作。

updateContainer接受2個(gè)參數(shù),第一個(gè)參數(shù)是傳入的ReactElement(), 第二個(gè)參數(shù)是fiberRootNode。

主要是做3件事情:

  • 創(chuàng)建一個(gè)更新事件
  • 把更新事件推進(jìn)隊(duì)列中
  • 調(diào)用調(diào)度,開始更新
/**
 * ReactDOM.createRoot().render 中調(diào)用更新
 * 1. 創(chuàng)建update, 并將其推到enqueueUpdate中
 */
export function updateContainer(
  element: ReactElementType | null,
  root: FiberRootNode
) {
  const hostRootFiber = root.current;
  const update = createUpdate<ReactElementType | null>(element);
  enqueueUpdate(
    hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
    update
  );
  // 插入更新后,進(jìn)入調(diào)度
  scheduleUpdateOnFiber(hostRootFiber);
  return element;
}

創(chuàng)建更新createUpdate

實(shí)際上就是創(chuàng)建一個(gè)對(duì)象,由于初始化的時(shí)候傳入的是ReactElementType(), 所以返回的是App對(duì)應(yīng)的ReactElement對(duì)象

/**
 * 創(chuàng)建更新
 * @param {Action<State>} action
 * @returns {Update<State>}
 */
export const createUpdate = (action) => {
  return {
    action,
  };
};

將更新推進(jìn)隊(duì)列enqueueUpdate

接受2個(gè)參數(shù),第一個(gè)參數(shù)是我們創(chuàng)建一個(gè)更新隊(duì)列的引用,第二個(gè)是新增的隊(duì)列

/**
 * 更新update
 * @param {UpdateQueue<Action>} updateQueue
 * @param {Update<Action>} update
 */
export const enqueueUpdate = <State>(
  updateQueue: UpdateQueue<State>,
  update: Update<State>
) => {
  updateQueue.shared.pending = update;
};

執(zhí)行到這一步驟,我們得到了更新隊(duì)列,其實(shí)是一個(gè)ReactElement組件 及我們調(diào)用render傳入的jsx對(duì)象。

開始調(diào)用scheduleUpdateOnFiber

接受FiberNode開始執(zhí)行我們的渲染工作, 一開始渲染傳入的是hostFiberNode 之后其他更新傳遞的是對(duì)應(yīng)的fiberNode

export function scheduleUpdateOnFiber(fiber: FiberNode) {
  // todo 調(diào)度功能
  let root = markUpdateFromFiberToRoot(fiber);
  renderRoot(root);
}

wookLoop

執(zhí)行完上面的操作后,接下來進(jìn)入的調(diào)和階段。開始我們要明白一個(gè)關(guān)鍵詞:

workInProgress: 表示當(dāng)前正在調(diào)和的fiber節(jié)點(diǎn),之后簡(jiǎn)稱wip

beginWork: 主要是根據(jù)當(dāng)前fiberNode創(chuàng)建下一級(jí)fiberNode,在update時(shí)標(biāo)記placement(新增、移動(dòng))ChildDeletion(刪除)

completeWork: 在mount時(shí)構(gòu)建Dom Tree, 初始化屬性,在Update時(shí)標(biāo)記Update(屬性更新),最終執(zhí)行flags冒泡

flags冒泡我們下一節(jié)講。

從上面我們可以看到調(diào)用了scheduleUpdateOnFiber方法,開始從根部渲染頁面。scheduleUpdateOnFiber主要是執(zhí)行了2個(gè)方法:

markUpdateFromFiberToRoot: 由于我們更新的節(jié)點(diǎn)可能不是hostfiberNode, 這個(gè)方法就是不管傳入的是那個(gè)節(jié)點(diǎn),返回我們的根節(jié)點(diǎn)rootFiberNode

// 從當(dāng)前觸發(fā)更新的fiber向上遍歷到根節(jié)點(diǎn)fiber
function markUpdateFromFiberToRoot(fiber: FiberNode) {
  let node = fiber;
  let parent = node.return;
  while (parent !== null) {
    node = parent;
    parent = node.return;
  }
  if (node.tag === HostRoot) {
    return node.stateNode;
  }
  return null;
}

renderRoot: 這里是我們wookLoop的入口,也是調(diào)和完成后,將生成的fiberNode樹,賦值給finishedWork,并掛在根節(jié)點(diǎn)上,進(jìn)入commit的入口。

function renderRoot(root: FiberRootNode) {
  // 初始化,將workInProgress 指向第一個(gè)fiberNode
  prepareFreshStack(root);
  do {
    try {
      workLoop();
      break;
    } catch (e) {
      if (__DEV__) {
        console.warn("workLoop發(fā)生錯(cuò)誤", e);
      }
      workInProgress = null;
    }
  } while (true);
  const finishedWork = root.current.alternate;
  root.finishedWork = finishedWork;
  // wip fiberNode樹  樹中的flags執(zhí)行對(duì)應(yīng)的操作
  commitRoot(root);
}

prepareFreshStack函數(shù): 用于初始化當(dāng)前節(jié)點(diǎn)的wip, 并創(chuàng)建alternate 的雙緩存的建立。 由于我們開始的時(shí)候傳入的hostFiberNode, 經(jīng)過createWorkInProgress后,創(chuàng)建了一個(gè)新的fiberNode 并通過alternate相互指向。并賦值給wip

let workInProgress: FiberNode | null = null;
function prepareFreshStack(root: FiberRootNode) {
  workInProgress = createWorkInProgress(root.current, {});
}
export const createWorkInProgress = (
  current: FiberNode,
  pendingProps: Props
): FiberNode => {
  let wip = current.alternate;
  if (wip === null) {
    //mount
    wip = new FiberNode(current.tag, pendingProps, current.key);
    wip.stateNode = current.stateNode;
    wip.alternate = current;
    current.alternate = wip;
  } else {
    //update
    wip.pendingProps = pendingProps;
    // 清掉副作用(上一次更新遺留下來的)
    wip.flags = NoFlags;
    wip.subtreeFlags = NoFlags;
  }
  wip.type = current.type;
  wip.updateQueue = current.updateQueue;
  wip.child = current.child;
  wip.memoizedProps = current.memoizedProps;
  wip.memoizedState = current.memoizedState;
  return wip;
};

接下來我們來分析一下workLoop中到底是如何生成fiberNode樹的。它本身函數(shù)執(zhí)行很簡(jiǎn)單。就是不停的根據(jù)wip進(jìn)行單個(gè)fiberNode的處理。 此時(shí)wip指向的hostRootFiber。開始執(zhí)行performUnitOfWork進(jìn)行遞歸操作,其中遞:beginWork,歸:completeWork。React通過DFS,首先找到對(duì)應(yīng)的葉子節(jié)點(diǎn)。

function workLoop() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}
function performUnitOfWork(fiber: FiberNode): void {
  const next = beginWork(fiber); // next 是fiber的子fiber 或者 是null
  // 工作完成,需要將pendingProps 復(fù)制給 已經(jīng)渲染的props
  fiber.memoizedProps = fiber.pendingProps;
  if (next === null) {
    // 沒有子fiber
    completeUnitOfWork(fiber);
  } else {
    workInProgress = next;
  }
}

beginWork開始

主要是向下進(jìn)行遍歷,創(chuàng)建不同的fiberNode。由于我們傳入的是HostRoot,所以會(huì)走到updateHostRoot分支

/**
 * 遞歸中的遞階段
 * 比較 然后返回子fiberNode 或者null
 */
export const beginWork = (wip: FiberNode) => {
  switch (wip.tag) {
    case HostRoot:
      return updateHostRoot(wip);
    case HostComponent:
      return updateHostComponent(wip);
    case HostText:
      // 文本節(jié)點(diǎn)沒有子節(jié)點(diǎn),所以沒有流程
      return null;
    default:
      if (__DEV__) {
        console.warn("beginWork未實(shí)現(xiàn)的類型");
      }
      break;
  }
  return null;
};

updateHostRoot

這個(gè)方法主要是2個(gè)部分:

  • 根據(jù)我們之前創(chuàng)建的更新隊(duì)列獲取到最新的值
  • 創(chuàng)建子fiber
/**
	processUpdateQueue: 是根據(jù)不同的類型(函數(shù)和其他)生成memoizedState
*/
function updateHostRoot(wip: FiberNode) {
  const baseState = wip.memoizedState;
  const updateQueue = wip.updateQueue as UpdateQueue<ElementType>;
  // 這里獲取之前的更新隊(duì)列
  const pending = updateQueue.shared.pending;
  updateQueue.shared.pending = null;
  const { memoizedState } = processUpdateQueue(baseState, pending); // 最新狀態(tài)
  wip.memoizedState = memoizedState; // 其實(shí)就是傳入的element
  const nextChildren = wip.memoizedState; // 就是我們傳入的ReactElement 對(duì)象
  reconcileChildren(wip, nextChildren);
  return wip.child;
}

reconcileChildren

調(diào)和子節(jié)點(diǎn), 根據(jù)是否生成過,分別調(diào)用不同的方法。通過上面我們知道傳入的hostFiber, 此時(shí)是存在alternate屬性的,所以會(huì)走到reconcilerChildFibers分支。

根據(jù)當(dāng)前傳入的returnFiberhostFiberNode以及currentFiber為null,newChild為ReactElementType。我們可以判斷接下來會(huì)走到reconcileSingleElement的執(zhí)行。其中placeSingleChild是打標(biāo)記使用的,我們暫時(shí)先不研究。

/**
	wip: 當(dāng)前正在執(zhí)行的父fiberNode
	children: 即將要生成的子fiberNode
*/
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
  const current = wip.alternate;
  if (current !== null) {
    // update
    wip.child = reconcilerChildFibers(wip, current?.child, children);
  } else {
    // mount
    wip.child = mountChildFibers(wip, null, children);
  }
}
function reconcilerChildFibers(
    returnFiber: FiberNode,
    currentFiber: FiberNode | null,
    newChild?: ReactElementType | string | number
  ) {
    // 判斷當(dāng)前fiber的類型
    if (typeof newChild === "object" && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(returnFiber, currentFiber, newChild)
          );
        default:
          if (__DEV__) {
            console.warn("未實(shí)現(xiàn)的reconcile類型", newChild);
          }
          break;
      }
    }
    // Todo 多節(jié)點(diǎn)的情況 ul > li * 3
    // HostText
    if (typeof newChild === "string" || typeof newChild === "number") {
      return placeSingleChild(
        reconcileSingleTextNode(returnFiber, currentFiber, newChild)
      );
    }
    if (__DEV__) {
      console.warn("未實(shí)現(xiàn)的reconcile類型", newChild);
    }
    return null;
  };
}

reconcileSingleElement

從名字我們可以看出是通過ReactElement 創(chuàng)建單一的fiberNode。通過reconcileSingleElement我們就可以得出了一個(gè)新的子節(jié)點(diǎn),然后通過return指向父fiber。此時(shí)的fiberNode樹如下圖。

  /**
   * 根據(jù)reactElement對(duì)象創(chuàng)建fiber并返回
   */
  function reconcileSingleElement(
    returnFiber: FiberNode,
    _currentFiber: FiberNode | null,
    element: ReactElementType
  ) {
    const fiber = createFiberFromElement(element);
    fiber.return = returnFiber;
    return fiber;
  }
export function createFiberFromElement(element: ReactElementType): FiberNode {
  const { type, key, props } = element;
  let fiberTag: WorkTag = FunctionComponent;
  if (typeof type === "string") {
    // <div/>  type : 'div'
    fiberTag = HostComponent;
  } else if (typeof type !== "function" && __DEV__) {
    console.log("未定義的type類型", element);
  }
  const fiber = new FiberNode(fiberTag, props, key);
  fiber.type = type;
  return fiber;
}

調(diào)用完后,此時(shí)回到了reconcileChildren函數(shù)的這一句代碼執(zhí)行,指定wip的child指向。此時(shí)函數(shù)執(zhí)行完畢。

// 省略無關(guān)代碼
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
    wip.child = reconcilerChildFibers(wip, current?.child, children);
}

執(zhí)行完后返回updateHostRoot函數(shù)調(diào)用reconcileChildren的地方。然后返回wip的child。

  function updateHostRoot(wip) {
      const baseState = wip.memoizedState;
      reconcileChildren(wip, nextChildren);
      return wip.child;
  }

執(zhí)行完updateHostRoot函數(shù)后,返回調(diào)用它的beginWork中。beginWork也同樣返回了當(dāng)前wip的child節(jié)點(diǎn)。

export const beginWork = (wip: FiberNode) => {
  switch (wip.tag) {
    case HostRoot:
      return updateHostRoot(wip);
  }
}

執(zhí)行完后,我們最后又回到了最開始調(diào)用beginWork的地方。進(jìn)行接下來的操作,主要是將已經(jīng)渲染過的屬性賦值。然后將wip賦值給下一個(gè)剛剛生成的子節(jié)點(diǎn)。以便于開始下一次的遞歸中調(diào)用。

  function performUnitOfWork(fiber) {
      const next = beginWork(fiber); // next 是fiber的子fiber 或者 是null
      // 工作完成,需要將pendingProps 復(fù)制給 已經(jīng)渲染的props
      fiber.memoizedProps = fiber.pendingProps;
      if (next === null) {
          // 沒有子fiber
          completeUnitOfWork(fiber);
      }
      else {
          workInProgress = next;
      }
  }

由于workInProgress不等于null, 說明還有子節(jié)點(diǎn)。繼續(xù)進(jìn)行workLoop調(diào)用。又開始了新的一輪。直到我們到達(dá)了葉子節(jié)點(diǎn)。

  function workLoop() {
      while (workInProgress !== null) {
          performUnitOfWork(workInProgress);
      }
  }

例子

例如,如下例子,當(dāng)遍歷到hcc文本節(jié)點(diǎn)后,由于我們節(jié)點(diǎn)是沒有調(diào)和流程的。所以執(zhí)行到beginWork后,返回了一個(gè)null。正式結(jié)束了遞歸調(diào)用中的“遞" 過程。此時(shí)的fiberNode樹如下圖所示。

const jsx = <div><span>hcc</span></div>
const root = document.querySelector('#root')
ReactDOM.createRoot(root).render(jsx)

completeWork開始

從上面的beginWork操作后,此時(shí)我們wip在文本節(jié)點(diǎn)hcc的節(jié)點(diǎn)位置.

completeUnitOfWork

接下來執(zhí)行performUnitOfWork中的completeUnitOfWork的邏輯部分,我們看看completeUnitOfWork的邏輯部分。 我們傳入的最底部的葉子節(jié)點(diǎn)。首先會(huì)對(duì)當(dāng)前節(jié)點(diǎn)進(jìn)行completeWork的方法調(diào)用。

function completeUnitOfWork(fiber) {
  let node = fiber;
  do {
      completeWork(node);
      const sibling = node.sibling;
      if (sibling !== null) {
          workInProgress = sibling;
          return;
      }
      node = node.return;
      workInProgress = node;
  } while (node !== null);
}

completeWork

首次我們會(huì)接受到一個(gè)最底部的子fiberNode,由于是第一次mount,所以當(dāng)前的fiber下不會(huì)存在alternate屬性的,所以會(huì)走到構(gòu)建Dom的流程。

/**
 * 遞歸中的歸
 */
export const completeWork = (wip: FiberNode) => {
  const newProps = wip.pendingProps;
  const current = wip.alternate;
  switch (wip.tag) {
    case HostComponent:
      if (current !== null && wip.stateNode) {
        //update
      } else {
        // 1. 構(gòu)建DOM
        const instance = createInstance(wip.type, newProps);
        // 2. 將DOM插入到DOM樹中
        appendAllChildren(instance, wip);
        wip.stateNode = instance;
      }
      bubbleProperties(wip);
      return null;
    case HostText:
      if (current !== null && wip.stateNode) {
        //update
      } else {
        // 1. 構(gòu)建DOM
        const instance = createTextInstance(newProps.content);
        // 2. 將DOM插入到DOM樹中
        wip.stateNode = instance;
      }
      bubbleProperties(wip);
      return null;
    case HostRoot:
      bubbleProperties(wip);
      return null;
    default:
      if (__DEV__) {
        console.warn("未實(shí)現(xiàn)的completeWork");
      }
      break;
  }
};
// 根據(jù)邏輯判斷,走到下面的邏輯判斷,傳入了文本
// 1. 構(gòu)建DOM 
const instance = createTextInstance(newProps.content); 
// 2. 將DOM插入到DOM樹中 
wip.stateNode = instance;

經(jīng)過completeWork后,我們給當(dāng)前的wip添加了stateNode屬性,用于指向生成的Dom節(jié)點(diǎn)。 執(zhí)行完completeWork后,繼續(xù)返回到completeUnitOfWork中,查找sibling節(jié)點(diǎn),目前我們demo中沒有,所以會(huì)向上找到當(dāng)前節(jié)點(diǎn)的return指向。繼續(xù)執(zhí)行completeWork工作,此時(shí)的結(jié)構(gòu)變成了如下圖:

由于我們wip目前是HostComponent, 所以走到了如下的completeWork的邏輯。這里 根據(jù)type創(chuàng)建不同的Dom元素,和之前一樣,綁定到對(duì)應(yīng)的stateNode屬性上。我們可以看到除了這2個(gè),還執(zhí)行了一個(gè)函數(shù)appendAllChildren。我們?nèi)タ纯催@個(gè)函數(shù)的作用是什么

// 1. 構(gòu)建DOM
const instance = createInstance(wip.type);
// 2. 將DOM插入到DOM樹中
appendAllChildren(instance, wip);
wip.stateNode = instance;

appendAllChildren

接受2個(gè)參數(shù),第一個(gè)是剛剛通過wip的type生成的對(duì)應(yīng)的dom, 另外一個(gè)是wip本身。 它的作用就是把我們上一步產(chǎn)生的dom節(jié)點(diǎn),插入到剛剛產(chǎn)生的父dom節(jié)點(diǎn)上,形成一個(gè)局部的小dom樹。

它本身存在一個(gè)復(fù)雜的遍歷過程,因?yàn)?code>fiberNode的層級(jí)和DOM元素的層級(jí)可能不是一一對(duì)應(yīng)的。

/**
 * 在parent的節(jié)點(diǎn)下,插入wip
 * @param {FiberNode} parent
 * @param {FiberNode} wip
 */
function appendAllChildren(parent: Container, wip: FiberNode) {
  let node = wip.child;
  while (node !== null) {
    if (node?.tag === HostComponent || node?.tag === HostText) {
      appendInitialChild(parent, node?.stateNode);
    } else if (node.child !== null) {
      node.child.return = node;
      // 繼續(xù)向下查找
      node = node.child;
      continue;
    }
    if (node === wip) {
      return;
    }
    while (node.sibling === null) {
      if (node.return === null || node.return === wip) {
        return;
      }
      // 向上找
      node = node?.return;
    }
    node.sibling.return = node.return;
    node = node.sibling;
  }
}

我們用這個(gè)圖來說明一下流程

  • 當(dāng)前的”歸“到了div對(duì)應(yīng)的fiberNode。我們獲取到node是第一個(gè)子元素的span, 執(zhí)行appendInitialChild方法,把對(duì)應(yīng)的stateNode的dom節(jié)點(diǎn)插入parent中。
  • 接下來執(zhí)行由于node.sibling不為空,所以會(huì)將node 復(fù)制給第二個(gè)span。然后繼續(xù)執(zhí)行appendInitialChild。以此執(zhí)行到第三個(gè)span節(jié)點(diǎn)。
  • 第三個(gè)span節(jié)點(diǎn)對(duì)應(yīng)的sibling為空,所以開始向上查找到node.return === wip結(jié)束函數(shù)調(diào)用。
  • 此時(shí)三個(gè)span產(chǎn)生的dom,都已經(jīng)插入到parent(div dom)中。

回到completeUnitOfWork

經(jīng)過上述操作后,我們繼續(xù)回到completeUnitOfWork的調(diào)用,繼續(xù)向上歸并。到上述例子的div節(jié)點(diǎn)。直到我們遍歷到hostFiberNode, 它是沒有return屬性的,所以返回null,結(jié)束了completeUnitOfWork的執(zhí)行?;氐搅俗铋_始的workLoop。此時(shí)的workInProgress等于null, 結(jié)束循環(huán)。

  function workLoop() {
      while (workInProgress !== null) {
          performUnitOfWork(workInProgress);
      }
  }

回到renderRoot

執(zhí)行完workLoop, 就回到了renderRoot的部分。此時(shí)我們已經(jīng)得到了完整的fiberNode樹,以及相應(yīng)的dom元素。此時(shí)對(duì)應(yīng)的結(jié)果如下圖:

那么生成的fiberNode樹是如何渲染的界面上的,我們下一章的commit章節(jié)介紹,如何打標(biāo)簽和渲染,更多關(guān)于React18系列reconciler實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • React如何避免子組件無效刷新

    React如何避免子組件無效刷新

    這篇文章主要介紹了React幾種避免子組件無效刷新的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-03-03
  • React中的setState使用細(xì)節(jié)和原理解析(最新推薦)

    React中的setState使用細(xì)節(jié)和原理解析(最新推薦)

    這篇文章主要介紹了React中的setState使用細(xì)節(jié)和原理解析(最新推薦),前面我們有使用過setState的基本使用, 接下來我們對(duì)setState使用進(jìn)行詳細(xì)的介紹,需要的朋友可以參考下
    2022-12-12
  • React和Vue的props驗(yàn)證示例詳解

    React和Vue的props驗(yàn)證示例詳解

    這篇文章主要介紹了React和Vue的props驗(yàn)證,本文通過示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-08-08
  • react-router browserHistory刷新頁面404問題解決方法

    react-router browserHistory刷新頁面404問題解決方法

    本篇文章主要介紹了react-router browserHistory刷新頁面404問題解決方法,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2017-12-12
  • react PropTypes校驗(yàn)傳遞的值操作示例

    react PropTypes校驗(yàn)傳遞的值操作示例

    這篇文章主要介紹了react PropTypes校驗(yàn)傳遞的值操作,結(jié)合實(shí)例形式分析了react PropTypes針對(duì)傳遞的值進(jìn)行校驗(yàn)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2020-04-04
  • 詳解如何在React中有效地監(jiān)聽鍵盤事件

    詳解如何在React中有效地監(jiān)聽鍵盤事件

    React是一種流行的JavaScript庫,用于構(gòu)建用戶界面,它提供了一種簡(jiǎn)單而靈活的方式來創(chuàng)建交互式的Web應(yīng)用程序,在React中,我們經(jīng)常需要監(jiān)聽用戶的鍵盤事件,以便根據(jù)用戶的輸入做出相應(yīng)的反應(yīng),本文將向您介紹如何在React中有效地監(jiān)聽鍵盤事件,并展示一些常見的應(yīng)用場(chǎng)景
    2023-11-11
  • React實(shí)現(xiàn)登錄表單的示例代碼

    React實(shí)現(xiàn)登錄表單的示例代碼

    這篇文章主要介紹了React實(shí)現(xiàn)登錄表單的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • react hooks實(shí)現(xiàn)防抖節(jié)流的方法小結(jié)

    react hooks實(shí)現(xiàn)防抖節(jié)流的方法小結(jié)

    這篇文章主要介紹了react hooks實(shí)現(xiàn)防抖節(jié)流的幾種方法,文中通過代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-04-04
  • React傳遞參數(shù)的幾種方式

    React傳遞參數(shù)的幾種方式

    本文詳細(xì)的介紹了React傳遞參數(shù)的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-06-06
  • 詳解在React里使用

    詳解在React里使用"Vuex"

    本篇文章主要介紹了詳解在React里使用"Vuex",小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-04-04

最新評(píng)論