国产成人精品18p,天天干成人网,无码专区狠狠躁天天躁,美女脱精光隐私扒开免费观看

教你如何從 html 實(shí)現一個(gè) react

發(fā)布時(shí)間:2021-08-17 12:16 來(lái)源: 閱讀:0 作者:blazer_id 欄目: JavaScript 歡迎投稿:712375056

什么是 React

React是一個(gè)簡(jiǎn)單的javascript UI庫,用于構建高效、快速的用戶(hù)界面。它是一個(gè)輕量級庫,因此很受歡迎。它遵循組件設計模式、聲明式編程范式和函數式編程概念,以使前端應用程序更高效。它使用虛擬DOM來(lái)有效地操作DOM。它遵循從高階組件到低階組件的單向數據流。

前言 📝

👉 我們認為,React 是用 JavaScript 構建快速響應的大型 Web 應用程序的首選方式。它在 Facebook 和 Instagram 上表現優(yōu)秀。

react 的理念是在于對大型項目的快速響應,對于新版的 react 16.8 而言更是帶來(lái)的全新的理念fiber去解決網(wǎng)頁(yè)快速響應時(shí)所伴隨的問(wèn)題,即 CPU 的瓶頸,傳統網(wǎng)頁(yè)瀏覽受制于瀏覽器刷新率、js 執行時(shí)間過(guò)長(cháng)等因素會(huì )造成頁(yè)面掉幀,甚至卡頓

react 由于自身的底層設計從而規避這一問(wèn)題的發(fā)生,所以 react16.8 的面世對于前端領(lǐng)域只辦三件事:快速響應、快速響應、還是 Tmd 快速響應 !,這篇文章將會(huì )從一個(gè) html 出發(fā),跟隨 react 的 fiber 理念,仿一個(gè)非?;A的 react

一開(kāi)始的準備工作 🤖

html

我們需要一個(gè) html 去撐起來(lái)整個(gè)頁(yè)面,支撐 react 運行,頁(yè)面中添加<div></div>,之后添加一個(gè) script 標簽,因為需要使用import進(jìn)行模塊化構建,所以需要為 script 添加 type 為module的屬性

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <div id="root"></div>
  <script type="module" src="./index.js" ></script>
</body>

</html>

推薦安裝一個(gè) Live Server 插件,有助于我們對代碼進(jìn)行調試,接下來(lái)的操作也會(huì )用到

JavaScript

我們會(huì )仿寫(xiě)一個(gè)如下的 react,實(shí)現一個(gè)基礎的操作,在 <input/> 綁定事件,將輸入的值插入在 <h2/> 標簽內:

...
function App() {
  return (
    <div>
      <input onInput={updateValue} value={value} />
      <h2>Hello {value}</h2>
      <hr />
    </div>
  );
}
...

在 react 進(jìn)行 babel 編譯的時(shí)候,會(huì )將 JSX 語(yǔ)法轉化為 React.createElement() 的形式,如上被 retuen 的代碼就會(huì )被轉換成

...
React.createElement(
  "div",
  null,
  React.createElement("input", {
    onInput: updateValue,
    value: value,
  }),
  React.createElement("h2", null, "Hello ", value),
  React.createElement("hr", null)
);
...

從轉換后的代碼我們可以看出 React.createElement 支持多個(gè)參數:

  • type,節點(diǎn)類(lèi)型
  • config, 節點(diǎn)上的屬性,比如 id 和 href
  • children, 子元素了,子元素可以有多個(gè),類(lèi)型可以是簡(jiǎn)單的文本,也可以還是 React.createElement,如果是 React.createElement,其實(shí)就是子節點(diǎn)了,子節點(diǎn)下面還可以有子節點(diǎn)。這樣就用 React.createElement 的嵌套關(guān)系實(shí)現了 HTML 節點(diǎn)的樹(shù)形結構。

我們可以按照 React.createElement 的形式仿寫(xiě)一個(gè)可以實(shí)現同樣功能的 createElement 將 jsx 通過(guò)一種簡(jiǎn)單的數據結構展示出來(lái)即 虛擬DOM 這樣在更新時(shí),新舊節點(diǎn)的對比也可以轉化為虛擬 DOM 的對比

{
  type:'節點(diǎn)標簽',
  props:{
    props:'節點(diǎn)上的屬性,包括事件、類(lèi)...',
    children:'節點(diǎn)的子節點(diǎn)'
  }
}

這里我們可以寫(xiě)一個(gè)函數實(shí)現下列需求

  • 原則是將所有的參數返回到一個(gè)對象上
  • children 也要放到 props 里面去,這樣我們在組件里面就能通過(guò) props.children 拿到子元素
  • 當子組件是文本節點(diǎn)時(shí),通過(guò)構造一種 type 為 TEXT_ELEMENT 的節點(diǎn)類(lèi)型表示
/**
 * 創(chuàng  )建虛擬 DOM 結構
 * @param {type} 標簽名
 * @param {props} 屬性對象
 * @param {children} 子節點(diǎn)
 * @return {element} 虛擬 DOM
 */
const createElement = (type, props, ...children) => ({
  type,
  props: {
    ...props,
    children: children.map(child =>
      typeof child === "object"
        ? child
        : {
            type: "TEXT_ELEMENT",
            props: {
              nodeValue: child,
              children: [],
            },
          }
    ),
  },
});

實(shí)現 createElement 之后我們可以拿到虛擬 DOM,但是還需要 render 將代碼渲染到頁(yè)面,此時(shí)我們需要對 index.js 進(jìn)行處理,添加輸入事件,將 createElementrender 通過(guò) import 進(jìn)行引入,render 時(shí)傳入被編譯后的虛擬 DOM 和頁(yè)面的根元素 root, 最后再進(jìn)行executeRender調用,頁(yè)面被渲染,在頁(yè)面更新的時(shí)候再次調用executeRender進(jìn)行更新渲染

import {createElement,render} from "./mini/index.js";
const updateValue = e => executeRender(e.target.value);
const executeRender = (value = "World") => {
  const element = createElement(
    "div",
    null,
    createElement("input", {
      onInput: updateValue,
      value: value,
    }),
    createElement("h2", null, "Hello ", value),
    createElement("hr", null)
  );
  render(element, document.getElementById("root"));
};

executeRender();

render 的時(shí)候做了什么 🥔

before 版本

render 函數幫助我們將 element 添加至真實(shí)節點(diǎn)中,首先它接受兩個(gè)參數:

根組件,其實(shí)是一個(gè) JSX 組件,也就是一個(gè) createElement 返回的虛擬 DOM

父節點(diǎn),也就是我們要將這個(gè)虛擬 DOM 渲染的位置

在 react 16.8 之前,渲染的方法是通過(guò)一下幾步進(jìn)行的

  • 創(chuàng )建 element.type 類(lèi)型的 dom 節點(diǎn),并添加到 root 元素下(文本節點(diǎn)特殊處理)
  • 將 element 的 props 添加到對應的 DOM 上,事件進(jìn)行特殊處理,掛載到 document 上(react17 調整為掛在到 container 上)
  • 將 element.children 循環(huán)添加至 dom 節點(diǎn)中;

拿到虛擬 dom 進(jìn)行如上三步的遞歸調用,渲染出頁(yè)面 類(lèi)似于如下流程

/**
 * 將虛擬 DOM 添加至真實(shí) DOM
 * @param {element} 虛擬 DOM
 * @param {container} 真實(shí) DOM
 */
const render = (element, container) => {
  let dom;
  /*
      處理節點(diǎn)(包括文本節點(diǎn))
  */
  if (typeof element !== "object") {
    dom = document.createTextNode(element);
  } else {
    dom = document.createElement(element.type);
  }
  /*
      處理屬性(包括事件屬性)
  */
  if (element.props) {
    Object.keys(element.props)
      .filter((key) => key != "children")
      .forEach((item) => {
        dom[item] = element.props[item];
      });
    Object.keys(element.props)
      .filter((key) => key.startsWith("on"))
      .forEach((name) => {
        const eventType = name.toLowerCase().substring(2);
        dom.addEventListener(eventType, nextProps[name]);
      });
  }
  if (
    element.props &&
    element.props.children &&
    element.props.children.length
  ) {
    /*
      循環(huán)添加到dom
  */
    element.props.children.forEach((child) => render(child, dom));
  }
  container.appendChild(dom);
};

after 版本(fiber)

當我們寫(xiě)完如上的代碼,會(huì )發(fā)現這個(gè)遞歸調用是有問(wèn)題的

如上這部分工作被 React 官方稱(chēng)為 renderer,renderer 是第三方可以自己實(shí)現的一個(gè)模塊,還有個(gè)核心模塊叫做 reconsiler,reconsiler 的一大功能就是 diff 算法,他會(huì )計算出應該更新哪些頁(yè)面節點(diǎn),然后將需要更新的節點(diǎn)虛擬 DOM 傳遞給 renderer,renderer 負責將這些節點(diǎn)渲染到頁(yè)面上,但是但是他卻是同步的,一旦開(kāi)始渲染,就會(huì )將所有節點(diǎn)及其子節點(diǎn)全部渲染完成這個(gè)進(jìn)程才會(huì )結束。

React 的官方演講中有個(gè)例子,可以很明顯的看到這種同步計算造成的卡頓:

當 dom tree 很大的情況下,JS 線(xiàn)程的運行時(shí)間可能會(huì )比較長(cháng),在這段時(shí)間瀏覽器是不會(huì )響應其他事件的,因為 JS 線(xiàn)程和 GUI 線(xiàn)程是互斥的,JS 運行時(shí)頁(yè)面就不會(huì )響應,這個(gè)時(shí)間太長(cháng)了,用戶(hù)就可能看到卡頓,

此時(shí)我們可以分為兩步解決這個(gè)問(wèn)題

  • 允許中斷渲染工作,如果有優(yōu)先級更高的工作插入,則暫時(shí)中斷瀏覽器渲染,待完成該工作后,恢復瀏覽器渲染;
  • 將渲染工作進(jìn)行分解,分解成一個(gè)個(gè)小單元;

solution I 引入一個(gè)新的 Api

requestIdleCallback 接收一個(gè)回調,這個(gè)回調會(huì )在瀏覽器空閑時(shí)調用,每次調用會(huì )傳入一個(gè) IdleDeadline,可以拿到當前還空余多久, options 可以傳入參數最多等多久,等到了時(shí)間瀏覽器還不空就強制執行了。

將在瀏覽器的空閑時(shí)段內調用的函數排隊。這使開(kāi)發(fā)者能夠在主事件循環(huán)上執行后臺和低優(yōu)先級工作,而不會(huì )影響延遲關(guān)鍵事件

但是這個(gè) API 還在實(shí)驗中,兼容性不好,所以 React 官方自己實(shí)現了一套。本文會(huì )繼續使用 requestIdleCallback 來(lái)進(jìn)行任務(wù)調度

// 下一個(gè)工作單元
let nextUnitOfWork = null
/**
 * workLoop 工作循環(huán)函數
 * @param {deadline} 截止時(shí)間
 */
function workLoop(deadline) {
  // 是否應該停止工作循環(huán)函數
  let shouldYield = false

  // 如果存在下一個(gè)工作單元,且沒(méi)有優(yōu)先級更高的其他工作時(shí),循環(huán)執行
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )

    // 如果截止時(shí)間快到了,停止工作循環(huán)函數
    shouldYield = deadline.timeRemaining() < 1
  }

  // 通知瀏覽器,空閑時(shí)間應該執行 workLoop
  requestIdleCallback(workLoop)
}
// 通知瀏覽器,空閑時(shí)間應該執行 workLoop
requestIdleCallback(workLoop)

// 執行單元事件,并返回下一個(gè)單元事件
function performUnitOfWork(nextUnitOfWork) {
  // TODO
}

solution II 創(chuàng )建 fiber 的數據結構

Fiber 之前的數據結構是一棵樹(shù),父節點(diǎn)的 children 指向了子節點(diǎn),但是只有這一個(gè)指針是不能實(shí)現中斷繼續的。比如我現在有一個(gè)父節點(diǎn) A,A 有三個(gè)子節點(diǎn) B,C,D,當我遍歷到 C 的時(shí)候中斷了,重新開(kāi)始的時(shí)候,其實(shí)我是不知道 C 下面該執行哪個(gè)的,因為只知道 C,并沒(méi)有指針指向他的父節點(diǎn),也沒(méi)有指針指向他的兄弟。

Fiber 就是改造了這樣一個(gè)結構,加上了指向父節點(diǎn)和兄弟節點(diǎn)的指針:

  • child 指向子組件
  • sibling 指向兄弟組件
  • return 指向父組件

每個(gè) fiber 都有一個(gè)鏈接指向它的第一個(gè)子節點(diǎn)、下一個(gè)兄弟節點(diǎn)和它的父節點(diǎn)。這種數據結構可以讓我們更方便的查找下一個(gè)工作單元,假定 A 是掛在 root 上的節點(diǎn) fiber 的渲染順序也如下步驟

  • 從 root 開(kāi)始,找到第一個(gè)子節點(diǎn) A;
  • 找到 A 的第一個(gè)子節點(diǎn) B
  • 找到 B 的第一個(gè)子節點(diǎn) E
  • 找 E 的第一個(gè)子節點(diǎn),如無(wú)子節點(diǎn),則找下一個(gè)兄弟節點(diǎn),找到 E 的兄弟節點(diǎn) F
  • 找 F 的第一個(gè)子節點(diǎn),如無(wú)子節點(diǎn),也無(wú)兄弟節點(diǎn),則找它的父節點(diǎn)的下一個(gè)兄弟節點(diǎn),找到 F 的 父節點(diǎn)的兄弟節點(diǎn) C;
  • 找 C 的第一個(gè)子節點(diǎn),找不到,找兄弟節點(diǎn),D
  • 找 D 的第一個(gè)子節點(diǎn),G
  • 找 G 的第一個(gè)子節點(diǎn),找不到,找兄弟節點(diǎn),找不到,找父節點(diǎn) D 的兄弟節點(diǎn),也找不到,繼續找 D 的父節點(diǎn)的兄弟節點(diǎn),找到 root;
  • 上一步已經(jīng)找到了 root 節點(diǎn),渲染已全部完成。

我們通過(guò)這個(gè)數據結構實(shí)現一個(gè) fiber

//創(chuàng  )建最初的根fiber
 wipRoot = {
  dom: container,
  props: { children: [element] },
};
performUnitOfWork(wipRoot);

隨后調用performUnitOfWork自上而下構造整個(gè) fiber 樹(shù)

/**
 * performUnitOfWork用來(lái)執行任務(wù)
 * @param {fiber} 我們的當前fiber任務(wù)
 * @return {fiber} 下一個(gè)任務(wù)f(shuō)iber任務(wù)
 */
const  performUnitOfWork = fiber => {
  if (!fiber.dom) fiber.dom = createDom(fiber); // 創(chuàng  )建一個(gè)DOM掛載上去
  const elements = fiber.props.children; //當前元素下的所有同級節點(diǎn)
  // 如果有父節點(diǎn),將當前節點(diǎn)掛載到父節點(diǎn)上
  if (fiber.return) {
    fiber.return.dom.appendChild(fiber.dom);
  }

  let prevSibling = null;
  /*
      之后代碼中我們將把此處的邏輯進(jìn)行抽離
  */
  if (elements && elements.length) {
    elements.forEach((element, index) => {
      const newFiber = {
        type: element.type,
        props: element.props,
        return: fiber,
        dom: null,
      };
      // 父級的child指向第一個(gè)子元素
      if (index === 0) {
        fiber.child = newFiber;
      } else {
        // 每個(gè)子元素擁有指向下一個(gè)子元素的指針
        prevSibling.sibling = newFiber;
      }
      prevSibling = fiber;
    });
  }
  // 先找子元素,沒(méi)有子元素了就找兄弟元素
  // 兄弟元素也沒(méi)有了就返回父元素
  // 最后到根節點(diǎn)結束
  // 這個(gè)遍歷的順序是從上到下,從左到右
  if (fiber.child) {
    return fiber.child;
  } else {
    let nextFiber = fiber;
    while (nextFiber) {
      if (nextFiber.sibling) {
        return nextFiber.sibling;
      }
      nextFiber = nextFiber.return;
    }
  }
}

after 版本(reconcile)

currentRoot

reconcile 其實(shí)就是虛擬 DOM 樹(shù)的 diff 操作,將更新前的 fiber tree 和更新后的 fiber tree 進(jìn)行比較,得到比較結果后,僅對有變化的 fiber 對應的 dom 節點(diǎn)進(jìn)行更新。

  • 刪除不需要的節點(diǎn)
  • 更新修改過(guò)的節點(diǎn)
  • 添加新的節點(diǎn)

新增 currentRoot 變量,保存根節點(diǎn)更新前的 fiber tree,為 fiber 新增 alternate 屬性,保存 fiber 更新前的 fiber tree

let currentRoot = null
function render (element, container) {
    wipRoot = {
        // 省略
        alternate: currentRoot
    }
}
function commitRoot () {
    commitWork(wipRoot.child)
    /*
        更改fiber樹(shù)的指向,將緩存中的fiber樹(shù)替換到頁(yè)面中的fiber tree
    */
    currentRoot = wipRoot
    wipRoot = null
}
  • 如果新老節點(diǎn)類(lèi)型一樣,復用老節點(diǎn) DOM,更新 props
  • 如果類(lèi)型不一樣,而且新的節點(diǎn)存在,創(chuàng )建新節點(diǎn)替換老節點(diǎn)
  • 如果類(lèi)型不一樣,沒(méi)有新節點(diǎn),有老節點(diǎn),刪除老節點(diǎn)

reconcileChildren

  • 將 performUnitOfWork 中關(guān)于新建 fiber 的邏輯,抽離到 reconcileChildren 函數
  • 在 reconcileChildren 中對比新舊 fiber;

在對比 fiber tree 時(shí)

  • 當新舊 fiber 類(lèi)型相同時(shí) 保留 dom,僅更新 props,設置 effectTag 為 UPDATE;
  • 當新舊 fiber 類(lèi)型不同,且有新元素時(shí) 創(chuàng )建一個(gè)新的 dom 節點(diǎn),設置 effectTag 為 PLACEMENT;
  • 當新舊 fiber 類(lèi)型不同,且有舊 fiber 時(shí) 刪除舊 fiber,設置 effectTag 為 DELETION
/**
 * 協(xié)調子節點(diǎn)
 * @param {fiber} fiber
 * @param {elements} fiber 的 子節點(diǎn)
 */
function reconcileChildren(wipFiber, elements) {
  let index = 0;// 用于統計子節點(diǎn)的索引值
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child; //更新時(shí)才會(huì )產(chǎn)生
  let prevSibling;// 上一個(gè)兄弟節點(diǎn)
  while (index < elements.length || oldFiber) {
    /**
     * 遍歷子節點(diǎn)
     * oldFiber判斷是更新觸發(fā)還是首次觸發(fā),更新觸發(fā)時(shí)為元素下所有節點(diǎn)
     */
    let newFiber;
    const element = elements[index];
    const sameType = oldFiber && element && element.type == oldFiber.type; // fiber 類(lèi)型是否相同點(diǎn)
    /**
     * 更新時(shí)
     * 同標簽不同屬性,更新屬性
     */
    if (sameType) {
      newFiber = {
        type: oldFiber.type,
        props: element.props, //只更新屬性
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber,
        effectTag: "UPDATE",
      };
    }
    /**
     * 不同標簽,即替換了標簽 or 創(chuàng  )建新標簽
     */
    if (element && !sameType) {
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "PLACEMENT",
      };
    }
    /**
     * 節點(diǎn)被刪除了
     */
    if (oldFiber && !sameType) {
      oldFiber.effectTag = "DELETION";
      deletions.push(oldFiber);
    }

    if (oldFiber) oldFiber = oldFiber.sibling;
    // 父級的child指向第一個(gè)子元素
    if (index === 0) {
      // fiber的第一個(gè)子節點(diǎn)是它的子節點(diǎn)
      wipFiber.child = newFiber;
    } else {
      // fiber 的其他子節點(diǎn),是它第一個(gè)子節點(diǎn)的兄弟節點(diǎn)
      prevSibling.sibling = newFiber;
    }
    // 把新建的 newFiber 賦值給 prevSibling,這樣就方便為 newFiber 添加兄弟節點(diǎn)了
    prevSibling = newFiber;
    //  索引值 + 1
    index++;
  }
}

在 commit 時(shí),根據 fiber 節點(diǎn)上effectTag的屬性執行不同的渲染操作

after 版本(commit)

在 commitWork 中對 fiber 的 effectTag 進(jìn)行判斷,處理真正的 DOM 操作。

  • 當 fiber 的 effectTag 為 PLACEMENT 時(shí),表示是新增 fiber,將該節點(diǎn)新增至父節點(diǎn)中。
  • 當 fiber 的 effectTag 為 DELETION 時(shí),表示是刪除 fiber,將父節點(diǎn)的該節點(diǎn)刪除。
  • 當 fiber 的 effectTag 為 UPDATE 時(shí),表示是更新 fiber,更新 props 屬性。
/**
 * @param {fiber} fiber 結構的虛擬dom
 */
function commitWork(fiber) {
  if (!fiber) return;
  const domParent = fiber.parent.dom;
  if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {
    domParent.removeChild(fiber.dom);
  }

  // 遞歸操作子元素和兄弟元素
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

此時(shí)我們著(zhù)重來(lái)看updateDom發(fā)生了什么,我們拿到 dom 上被改變的新舊屬性,進(jìn)行操作

/*
    isEvent :拿到事件屬性
    isProperty :拿到非節點(diǎn)、非事件屬性
    isNew :拿到前后改變的屬性
*/
const isEvent = key => key.startsWith("on");
const isProperty = key => key !== "children" && !isEvent(key);
const isNew = (prev, next) => key => prev[key] !== next[key];


/**
 * 更新dom屬性
 * @param {dom} fiber dom
 * @param {prevProps} fiber dom上舊的屬性
 * @param {nextProps} fiber dom上新的屬性
 */
function updateDom(dom, prevProps, nextProps) {
  /**
   * 便利舊屬性
   * 1、拿到on開(kāi)頭的事件屬性
   * 2、拿到被刪除的事件
   * 3、已刪除的事件取消監聽(tīng)
   */
  Object.keys(prevProps)
    .filter(isEvent)
    .filter(key => !(key in nextProps))
    .forEach(name => {
      const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  /**
   * 便利舊屬性
   * 1、拿到非事件屬性和非子節點(diǎn)的屬性
   * 2、拿到被刪除的屬性
   * 3、刪除屬性
   */
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(key => !(key in nextProps))
    .forEach(key => delete dom[key]);

  /**
   * 便利新屬性
   * 1、拿到非事件屬性和非子節點(diǎn)的屬性
   * 2、拿到前后改變的屬性
   * 3、添加屬性
   */
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      dom[name] = nextProps[name];
    });

  /**
   * 便利新屬性
   * 1、拿到on開(kāi)頭的事件屬性
   * 2、拿到前后改變的事件屬性
   * 3、為新增的事件屬性添加監聽(tīng)
   */
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach(name => {
      const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}

完成了一系列對 dom 的操作,我們將新改變的 dom 渲染到頁(yè)面,當 input 事件執行時(shí),頁(yè)面又會(huì )進(jìn)行渲染,但此時(shí)會(huì )進(jìn)入更新 fiber 樹(shù)的邏輯,
alternate 指向之前的 fiber 節點(diǎn)進(jìn)行復用,更快的執行 Update 操作,如圖:

大功告成!

完整代碼可以看我。

結論與總結 💢

結論

  1. 我們寫(xiě)的 JSX 代碼被 babel 轉化成了 React.createElement。
  2. React.createElement 返回的其實(shí)就是虛擬 DOM 結構。
  3. 虛擬 DOM 的調和和渲染可以簡(jiǎn)單粗暴的遞歸,但是這個(gè)過(guò)程是同步的,如果需要處理的節點(diǎn)過(guò)多,可能會(huì )阻塞用戶(hù)輸入和動(dòng)畫(huà)播放,造成卡頓。
  4. Fiber 是 16.x 引入的新特性,用處是將同步的調和變成異步的。
  5. Fiber 改造了虛擬 DOM 的結構,具有 父->第一個(gè)子, 子->兄, 子->父這幾個(gè)指針,有了這幾個(gè)指針,可以從任意一個(gè) Fiber 節點(diǎn)找到其他節點(diǎn)。
  6. Fiber 將整棵樹(shù)的同步任務(wù)拆分成了每個(gè)節點(diǎn)可以單獨執行的異步執行結構。
  7. Fiber 可以從任意一個(gè)節點(diǎn)開(kāi)始遍歷,遍歷是深度優(yōu)先遍歷,順序是 父->子->兄->父,也就是從上往下,從左往右。
  8. Fiber 的調和階段可以是異步的小任務(wù),但是提交階段( commit)必須是同步的。因為異步的 commit 可能讓用戶(hù)看到節點(diǎn)一個(gè)一個(gè)接連出現,體驗不好。

總結

  • react hook 實(shí)現 ✖
  • react 合成事件 ✖
  • 還有很多沒(méi)有實(shí)現 😤...

至此,謝謝各位在百忙之中點(diǎn)開(kāi)這篇文章,希望對你們能有所幫助,如有問(wèn)題歡迎各位大佬指正。工作原因這篇文章大概斷斷續續寫(xiě)了有一個(gè)月,工作上在忙一個(gè)基于 騰訊云TRTC+websocket 的小程序電話(huà)功能,有時(shí)間也會(huì )寫(xiě)成文章分享一下,當然 react 的實(shí)現文章也會(huì )繼續

👋: 歡迎給個(gè) star,謝謝大家了

參考文獻

🍑:

🍑:

🍑:

🍑:

🍑:

🍑:

🍑:

到此這篇關(guān)于教你如何從 html 實(shí)現一個(gè) react的文章就介紹到這了,更多相關(guān) html 實(shí)現react內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。

图片小说视频一区二区 | 日产又大又黄又爽又猛| 免费播放很黄很色毛片| 最新永久无码AV网址亚洲| 日韩AV片免费播放| 亚洲精品国精品久久99热一|