掃二維碼與項(xiàng)目經(jīng)理溝通
我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流
由于近期在看React框架源碼、底層實(shí)現(xiàn)方面的知識(shí),所以想把學(xué)習(xí)心得整理出來(lái)。

10余年專注成都網(wǎng)站制作,企業(yè)網(wǎng)站制作,個(gè)人網(wǎng)站制作服務(wù),為大家分享網(wǎng)站制作知識(shí)、方案,網(wǎng)站設(shè)計(jì)流程、步驟,成功服務(wù)上千家企業(yè)。為您提供網(wǎng)站建設(shè),網(wǎng)站制作,網(wǎng)頁(yè)設(shè)計(jì)及定制高端網(wǎng)站建設(shè)服務(wù),專注于企業(yè)網(wǎng)站制作,高端網(wǎng)頁(yè)制作,對(duì)成都葡萄架等多個(gè)領(lǐng)域,擁有豐富建站經(jīng)驗(yàn)。
這也是一個(gè)新的系列「從0實(shí)現(xiàn)React 18核心模塊」的第一篇。
接下來(lái)還會(huì)更新:render、commit階段的實(shí)現(xiàn),以及Hooks架構(gòu)、useState、useEffect、單雙節(jié)點(diǎn)Diff的過(guò)程還有React 18中的并發(fā)更新原理。
在看文章之前,我們可以先想幾個(gè)問(wèn)題:
如果自己實(shí)現(xiàn)一個(gè) React 框架,它需要包含哪些內(nèi)置的包:
如果還有一個(gè)必要的包,那就是react-dom:
當(dāng)我們?cè)陧?xiàng)目中使用 React 構(gòu)建界面時(shí),主要使用的就是 react? 包。它提供了開(kāi)發(fā)者需要的所有API。如React.Component、React.createElement、React.useState等等,所以它也是大多數(shù) React 項(xiàng)目的基礎(chǔ)。
react-reconciler包是一個(gè)更底層、更高級(jí)的庫(kù),它實(shí)現(xiàn)了reconciliation協(xié)調(diào)算法,reconciliation是 React 的一種核心優(yōu)化策略,用于在更新組件時(shí)比較虛擬DOM樹(shù)的差異,并將實(shí)際更改應(yīng)用到實(shí)際的DOM樹(shù)。這有助于提高性能,因?yàn)楸苊饬瞬槐匾腄OM操作。
它主要用于創(chuàng)建自定義渲染器,以及在不同的平臺(tái)中去使用 React。例如,react-dom(用于Web平臺(tái))和react-native(用于移動(dòng)應(yīng)用)都使用react-reconciler作為底層庫(kù),實(shí)現(xiàn)了針對(duì)各自平臺(tái)的渲染邏輯。
const element =Hello, world!;
在React中,JSX是一種JavaScript語(yǔ)法擴(kuò)展,允許你在JavaScript代碼中編寫類似HTML的標(biāo)記。要使用JSX,需要在構(gòu)建過(guò)程中將其轉(zhuǎn)換為標(biāo)準(zhǔn)的JavaScript代碼。
通常,這個(gè)轉(zhuǎn)換過(guò)程包括兩個(gè)主要部分:
在React 17之前,JSX語(yǔ)法會(huì)被編譯成React.createElement函數(shù)的調(diào)用,用來(lái)創(chuàng)建虛擬DOM元素。
轉(zhuǎn)換結(jié)果如下:
const element = React.createElement(
"div",
{ className: "container" },
"Hello, world!"
);
從React 17開(kāi)始,引入了新的JSX轉(zhuǎn)換功能,稱為"Runtime Automatic"(自動(dòng)運(yùn)行時(shí))。這意味著在使用JSX語(yǔ)法時(shí),不再需要手動(dòng)引入React庫(kù)。在自動(dòng)運(yùn)行時(shí)模式下,JSX會(huì)被轉(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!"
});接下來(lái)我們就來(lái)實(shí)現(xiàn)jsx方法或React.createElement方法(包括dev、prod兩個(gè)環(huán)境)。
工作量包括:
jsx 轉(zhuǎn)換方法包括:
在React 17之前,JSX轉(zhuǎn)換應(yīng)用的是createElement方法,下面是它的實(shí)現(xiàn):
/**
*
* @param type 元素類型
* @param config 元素屬性,包括key,不包括子元素children
* @param maybeChildren 子元素children
* @returns 返回一個(gè)ReactElement
*/
const createElement = (
type: ElementType,
config: any,
...maybeChildren: any
) => {
// reactElement 自身的屬性
let key: Key = null;
let ref: Ref = null;
// 創(chuàng)建一個(gè)空對(duì)象props,用于存儲(chǔ)屬性
const props: Props = {};
// 遍歷config對(duì)象,將ref、key這些ReactElement內(nèi)部使用的屬性提取出來(lá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)在第三個(gè)參數(shù)上。
從React 17之后,JSX轉(zhuǎn)換應(yīng)用的是jsx方法,下面是它的實(shí)現(xiàn):
/**
*
* @param type 元素類型
* @param config 元素屬性
* @param maybeKey 可能的key值
* @returns 返回一個(gè)ReactElement
*/
const jsx = (type: ElementType, config: any, maybeKey: any) => {
// 初始化key和ref為空
let key = null;
let ref = null;
// 創(chuàng)建一個(gè)空對(duì)象props,用于存儲(chǔ)屬性
const props: Props = {};
// 遍歷config對(duì)象,將ref、key這些ReactElement內(nèi)部使用的屬性提取出來(lá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);
};
這段代碼定義了一個(gè)jsx函數(shù),主要用于創(chuàng)建React元素。首先,它會(huì)提取可能存在的key和ref屬性,并將剩余屬性添加到一個(gè)新的props對(duì)象中。最后用ReactElement函數(shù)創(chuàng)建一個(gè)React元素并返回。
從上面代碼中可以看到還實(shí)現(xiàn)了ReactElement方法:
// jsx-runtime.js
const supportSymbol = typeof Symbol === 'function' && Symbol.for;
// 為了不濫用 React.elemen,所以為它創(chuàng)建一個(gè)單獨(dú)的鍵
// 為React.element元素創(chuàng)建一個(gè) symbol 并放入到 symbol 注冊(cè)表中
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 =...
我們?cè)囍炎约簩?shí)現(xiàn)的jsx方法,創(chuàng)建一個(gè)ReactElement,看它是否能夠渲染在頁(yè)面上。
實(shí)現(xiàn)jsx方法
jsx-Demo運(yùn)行地址
jsx函數(shù)和createElement函數(shù)都用于在React中創(chuàng)建虛擬DOM元素,但它們的語(yǔ)法和用法有所不同。jsx函數(shù)來(lái)自于React 17及更高版本中的新的JSX轉(zhuǎn)換功能,稱為"Runtime Automatic"。
以下是兩者之間的主要區(qū)別:
例如,一個(gè)JSX元素:
const element =Hello, world!;
使用createElement轉(zhuǎn)換后的代碼如下:
const element = React.createElement(
"div",
{ className: "container" },
"Hello, world!"
);
使用jsx函數(shù)(自動(dòng)運(yùn)行時(shí))轉(zhuǎn)換后的代碼如下:
import { jsx as _jsx } from "react/jsx-runtime";
const element = _jsx("div", { className: "container", children: "Hello, world!" });在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");這時(shí)可能產(chǎn)生兩個(gè)疑問(wèn):
在之前的React版本中,每當(dāng)創(chuàng)建一個(gè)新的React元素時(shí),React都需要從屬性對(duì)象中提取key?和ref,這會(huì)導(dǎo)致額外的性能開(kāi)銷。
將key?作為單獨(dú)的參數(shù)傳遞,可以讓React在處理虛擬DOM樹(shù)時(shí)更容易地訪問(wèn)key,無(wú)需每次都從屬性對(duì)象中查找。這有助于提高React的性能和效率,特別是在處理大量元素和復(fù)雜組件樹(shù)時(shí)。
打包流程稍微有些復(fù)雜,后續(xù)寫到文章里。
簡(jiǎn)單來(lái)說(shuō)就是使用 Rollup,將編寫jsx方法的文件打包出來(lái),通過(guò)pnpm link --global的方式生成一個(gè)全局的react包,這樣就可以通過(guò)pnpm link react --global調(diào)試自己創(chuàng)建的 create-react-app demo項(xiàng)目了。
構(gòu)建react包思路

我們?cè)谖⑿派?4小時(shí)期待你的聲音
解答本文疑問(wèn)/技術(shù)咨詢/運(yùn)營(yíng)咨詢/技術(shù)建議/互聯(lián)網(wǎng)交流