自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

「深入淺出」實現(xiàn)JSX的轉(zhuǎn)換

開發(fā) 前端
當(dāng)我們在項目中使用 React 構(gòu)建界面時,主要使用的就是 React 包。它提供了開發(fā)者需要的所有API。如React.Component、React.createElement、React.useState等等,所以它也是大多數(shù) React 項目的基礎(chǔ)。

前言

由于近期在看React框架源碼、底層實現(xiàn)方面的知識,所以想把學(xué)習(xí)心得整理出來。

這也是一個新的系列「從0實現(xiàn)React 18核心模塊」的第一篇。

接下來還會更新:render、commit階段的實現(xiàn),以及Hooks架構(gòu)、useState、useEffect、單雙節(jié)點Diff的過程還有React 18中的并發(fā)更新原理。

在看文章之前,我們可以先想幾個問題:

  • JSX 是什么語法?
  • JSX 有什么優(yōu)勢,它的轉(zhuǎn)換規(guī)則是什么或者它內(nèi)部是如何實現(xiàn)的?
  • 既然 React 一直在使用 JSX,那它的實現(xiàn)被寫應(yīng)該寫在哪個包里(比如react、react-dom,react-reconciler)?
  • 在React 17之前和React 17之后,JSX轉(zhuǎn)換的方法實現(xiàn)有哪些異同?
  • 如何實現(xiàn)React.createElement方法和運行時的 jsx 方法?
  • 寫一個Demo引入自己實現(xiàn)的jsx方法,看看運行結(jié)果

下文提到的 big-react 是從0到1實現(xiàn)的React的核心功能模塊原理的項目

如果自己實現(xiàn)一個 React 框架,它需要包含哪些內(nèi)置的包:

  • react包是 React 的核心庫,提供了創(chuàng)建和管理組件所需的基本功能(比如組件創(chuàng)建、組件生命周期管理、虛擬DOM以及Hooks等),主要是一些和宿主環(huán)境無關(guān)的方法。
  • react-reconciler包實現(xiàn)了 React 的 reconciliation 協(xié)調(diào)算法,是一種核心優(yōu)化策略的實現(xiàn),主要自定義協(xié)調(diào)器的實現(xiàn)。以及在不同的平臺或環(huán)境中使用 React。
  • shared包是big-react公用的輔助方法,和宿主環(huán)境無關(guān)。

如果還有一個必要的包,那就是react-dom:

  • react-dom:這個包提供了將 React 與 DOM(瀏覽器環(huán)境)集成的方法。它包含了用于將 React 組件渲染到 DOM 中的 ReactDOM.render() 函數(shù),以及其他與瀏覽器環(huán)境相關(guān)的實用功能。對于在瀏覽器中運行的 React 應(yīng)用程序,react-dom 是必需的。

react與react-reconciler包是什么

react包為我們提供了什么

當(dāng)我們在項目中使用 React 構(gòu)建界面時,主要使用的就是 react? 包。它提供了開發(fā)者需要的所有API。如React.Component、React.createElement、React.useState等等,所以它也是大多數(shù) React 項目的基礎(chǔ)。

react-reconciler包實現(xiàn)了什么?

react-reconciler包是一個更底層、更高級的庫,它實現(xiàn)了reconciliation協(xié)調(diào)算法,reconciliation是 React 的一種核心優(yōu)化策略,用于在更新組件時比較虛擬DOM樹的差異,并將實際更改應(yīng)用到實際的DOM樹。這有助于提高性能,因為避免了不必要的DOM操作。

它主要用于創(chuàng)建自定義渲染器,以及在不同的平臺中去使用 React。例如,react-dom(用于Web平臺)和react-native(用于移動應(yīng)用)都使用react-reconciler作為底層庫,實現(xiàn)了針對各自平臺的渲染邏輯。

JSX 是什么

const element = <div className="container">Hello, world!</div>;

在React中,JSX是一種JavaScript語法擴展,允許你在JavaScript代碼中編寫類似HTML的標(biāo)記。要使用JSX,需要在構(gòu)建過程中將其轉(zhuǎn)換為標(biāo)準(zhǔn)的JavaScript代碼。

通常,這個轉(zhuǎn)換過程包括兩個主要部分:

  • 編譯時:通常指將 JSX 語法轉(zhuǎn)換為瀏覽器可以理解的普通 JavaScript 代碼的過程,這個過程通常由 Babel 完成。
  • 構(gòu)建時:在將JSX語法轉(zhuǎn)換為標(biāo)準(zhǔn)的JavaScript代碼后,通常會使用構(gòu)建和打包工具(如Webpack、Rollup)對代碼進行優(yōu)化、壓縮和打包。打包工具將源代碼和依賴項組合成一個或多個文件(“bundles”或“chunks”),用于在瀏覽器中運行。
  • 運行時:React會根據(jù)編譯后的代碼創(chuàng)建虛擬DOM樹,然后將其渲染到實際的DOM中。還會發(fā)生的階段有狀態(tài)管理和更新、事件處理和Diff算法的比較等。

JSX 被 Babel 編譯成了什么

在React 17之前,JSX語法會被編譯成React.createElement函數(shù)的調(diào)用,用來創(chuàng)建虛擬DOM元素。

轉(zhuǎn)換結(jié)果如下:

const element = React.createElement(
"div",
{ className: "container" },
"Hello, world!"
);

從React 17開始,引入了新的JSX轉(zhuǎn)換功能,稱為"Runtime Automatic"(自動運行時)。這意味著在使用JSX語法時,不再需要手動引入React庫。在自動運行時模式下,JSX會被轉(zhuǎn)換成新的入口函數(shù),import {jsx as _jsx} from 'react/jsx-runtime'; 和 import {jsxs as _jsxs} from 'react/jsx-runtime';。

轉(zhuǎn)換結(jié)果如下:

import { jsx as _jsx } from "react/jsx-runtime";

const element = _jsx("div", {
className: "container",
children: "Hello, world!"
});

接下來我們就來實現(xiàn)jsx方法或React.createElement方法(包括dev、prod兩個環(huán)境)。

工作量包括:

  • 實現(xiàn)jsx方法
  • 實現(xiàn)打包流程
  • 實現(xiàn)調(diào)試打包結(jié)果的環(huán)境

實現(xiàn) jsx 轉(zhuǎn)換方法

jsx 轉(zhuǎn)換方法包括:

  • React.createElement方法
  • jsxDEV方法(dev環(huán)境)
  • jsx方法(prod環(huán)境)

實現(xiàn)React.createElement

在React 17之前,JSX轉(zhuǎn)換應(yīng)用的是createElement方法,下面是它的實現(xiàn):

/**
*
* @param type 元素類型
* @param config 元素屬性,包括key,不包括子元素children
* @param maybeChildren 子元素children
* @returns 返回一個ReactElement
*/
const createElement = (
type: ElementType,
config: any,
...maybeChildren: any
) => {
// reactElement 自身的屬性
let key: Key = null;
let ref: Ref = null;

// 創(chuàng)建一個空對象props,用于存儲屬性
const props: Props = {};

// 遍歷config對象,將ref、key這些ReactElement內(nèi)部使用的屬性提取出來,不應(yīng)該被傳遞下去
for (const prop in config) {
const val = config[prop];
if (prop === 'key') {
if (val !== undefined) {
key = '' + val;
}
continue;
}
if (prop === 'ref') {
if (val !== undefined) {
ref = val;
}
continue;
}
// 去除config原型鏈上的屬性,只要自身
// 一般使用{...props}將所有屬性都傳遞下去,所以摘除ref、key屬性外需要被保存到props中
if ({}.hasOwnProperty.call(config, prop)) {
props[prop] = val;
}
}

const maybeChildrenLength = maybeChildren.length;
if (maybeChildrenLength) {
// [child] [child, child, child]
if (maybeChildrenLength === 1) {
props.children = maybeChildren[0];
} else {
props.children = maybeChildren;
}
}

return ReactElement(type, key, ref, props);
};

注意:React.createElement方法和jsx方法的區(qū)別這里只體現(xiàn)在第三個參數(shù)上。

實現(xiàn)jsx方法

從React 17之后,JSX轉(zhuǎn)換應(yīng)用的是jsx方法,下面是它的實現(xiàn):

/**
*
* @param type 元素類型
* @param config 元素屬性
* @param maybeKey 可能的key值
* @returns 返回一個ReactElement
*/
const jsx = (type: ElementType, config: any, maybeKey: any) => {
// 初始化key和ref為空
let key = null;
let ref = null;

// 創(chuàng)建一個空對象props,用于存儲屬性
const props: Props = {};

// 遍歷config對象,將ref、key這些ReactElement內(nèi)部使用的屬性提取出來,不應(yīng)該被傳遞下去
for (const prop in config) {
const val = config[prop];
if (prop === "key") {
continue;
}
if (prop === "ref") {
if (val !== undefined) {
ref = val;
}
continue;
}
// 一般使用{...props}將所有屬性都傳遞下去,所以摘除ref、key屬性外需要被保存到props中
if ({}.hasOwnProperty.call(config, prop)) {
props[prop] = val;
}
}

// 將 maybeKey 添加到 key 中
if (maybeKey !== undefined) {
key = "" + maybeKey;
}

return ReactElement(type, key, ref, props);
};

這段代碼定義了一個jsx函數(shù),主要用于創(chuàng)建React元素。首先,它會提取可能存在的key和ref屬性,并將剩余屬性添加到一個新的props對象中。最后用ReactElement函數(shù)創(chuàng)建一個React元素并返回。

從上面代碼中可以看到還實現(xiàn)了ReactElement方法:

// jsx-runtime.js
const supportSymbol = typeof Symbol === 'function' && Symbol.for;

// 為了不濫用 React.elemen,所以為它創(chuàng)建一個單獨的鍵
// 為React.element元素創(chuàng)建一個 symbol 并放入到 symbol 注冊表中
export const REACT_ELEMENT_TYPE = supportSymbol
? Symbol.for('react.element')
: 0xeac7;

export const ReactElement = function (type, key, ref, props) {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
_mark: 'lsh',
};
return element;
};

export const jsx =...

用自己實現(xiàn)的的jsx接入Demo

我們試著把自己實現(xiàn)的jsx方法,創(chuàng)建一個ReactElement,看它是否能夠渲染在頁面上。

圖片

實現(xiàn)jsx方法

jsx-Demo運行地址

jsx方法和createElement的區(qū)別

jsx函數(shù)和createElement函數(shù)都用于在React中創(chuàng)建虛擬DOM元素,但它們的語法和用法有所不同。jsx函數(shù)來自于React 17及更高版本中的新的JSX轉(zhuǎn)換功能,稱為"Runtime Automatic"。

以下是兩者之間的主要區(qū)別:

  1. 語法和轉(zhuǎn)換方式:jsx函數(shù)用于處理新的JSX轉(zhuǎn)換方式,其語法更簡潔。createElement函數(shù)用于處理傳統(tǒng)的JSX轉(zhuǎn)換方式。

例如,一個JSX元素:

const element = <div className="container">Hello, world!</div>;

使用createElement轉(zhuǎn)換后的代碼如下:

const element = React.createElement(
"div",
{ className: "container" },
"Hello, world!"
);

使用jsx函數(shù)(自動運行時)轉(zhuǎn)換后的代碼如下:

import { jsx as _jsx } from "react/jsx-runtime";

const element = _jsx("div", { className: "container", children: "Hello, world!" });
  1. ?子元素和key值處理:jsx函數(shù)將子元素作為屬性(children)傳遞,而createElement函數(shù)將子元素作為額外的參數(shù)傳遞。同時子元素上的key值在jsx函數(shù)中也會以第三個參數(shù)的形式傳遞,而在createElement函數(shù)中,則是存在于config第二個參數(shù)中。

在createElement函數(shù)中:

React.createElement("div", {className: "app", key: "appKey"}, "hello,app");

在jsx函數(shù)中:

import { jsx as _jsx } from "react/jsx-runtime";

_jsx("div", {className: "app", children: "hello,app"}, "appKey");
  1. ?兼容性和版本:createElement函數(shù)在所有React版本中可用,而jsx函數(shù)僅在React 17及更高版本中提供。盡管React團隊推薦使用新的JSX轉(zhuǎn)換方式,但許多現(xiàn)有項目可能仍在使用createElement函數(shù)。

這時可能產(chǎn)生兩個疑問:

  • 從React 17之后使用Runtime Automatic自動運行時有什么好處?
  1. 簡化組件代碼:不再需要在每個組件文件頂部添加**import React from 'react';**。這使得組件代碼更簡潔,更易于閱讀和維護。
  2. 節(jié)省包大?。河捎诓辉傩枰獙?dǎo)入整個React對象,構(gòu)建工具可以更好地優(yōu)化輸出代碼,從而減小輸出包的大小。
  • 改成jsx函數(shù)后,為什么要把key屬性單獨拿出來放在第三個參數(shù)?

在之前的React版本中,每當(dāng)創(chuàng)建一個新的React元素時,React都需要從屬性對象中提取key?和ref,這會導(dǎo)致額外的性能開銷。

將key?作為單獨的參數(shù)傳遞,可以讓React在處理虛擬DOM樹時更容易地訪問key,無需每次都從屬性對象中查找。這有助于提高React的性能和效率,特別是在處理大量元素和復(fù)雜組件樹時。

實現(xiàn)打包流程

打包流程稍微有些復(fù)雜,后續(xù)寫到文章里。

簡單來說就是使用 Rollup,將編寫jsx方法的文件打包出來,通過pnpm link --global的方式生成一個全局的react包,這樣就可以通過pnpm link react --global調(diào)試自己創(chuàng)建的 create-react-app demo項目了。

圖片

構(gòu)建react包思路

責(zé)任編輯:姜華 來源: 前端時光屋
相關(guān)推薦

2021-04-27 08:54:43

ConcurrentH數(shù)據(jù)結(jié)構(gòu)JDK8

2011-07-04 10:39:57

Web

2021-03-16 08:54:35

AQSAbstractQueJava

2022-09-26 09:01:15

語言數(shù)據(jù)JavaScript

2019-11-11 14:51:19

Java數(shù)據(jù)結(jié)構(gòu)Properties

2022-12-02 09:13:28

SeataAT模式

2009-11-30 16:46:29

學(xué)習(xí)Linux

2017-07-02 18:04:53

塊加密算法AES算法

2019-01-07 15:29:07

HadoopYarn架構(gòu)調(diào)度器

2021-07-20 15:20:02

FlatBuffers阿里云Java

2012-05-21 10:06:26

FrameworkCocoa

2019-11-14 09:53:30

Set集合存儲

2009-12-25 15:49:43

Linux rescu

2009-11-17 17:31:58

Oracle COMM

2021-07-19 11:54:15

MySQL優(yōu)先隊列

2023-12-04 13:22:00

JavaScript異步編程

2010-07-26 12:57:12

OPhone游戲開發(fā)

2016-10-14 13:53:05

JavascriptDOMWeb

2016-10-14 14:32:58

JavascriptDOMWeb

2010-07-16 09:11:40

JavaScript內(nèi)存泄漏
點贊
收藏

51CTO技術(shù)棧公眾號