前言:使用react也有二年多了,一直停留在使用層次。雖然很多時(shí)候這樣是夠了。但是總覺(jué)得不深入理解其背后是的實(shí)現(xiàn)邏輯,很難體會(huì)框架的精髓。最近會(huì)寫(xiě)一些相關(guān)的一些文章,來(lái)記錄學(xué)習(xí)的過(guò)程。
備注:react和react-dom源碼版本為16.8.6 本文適合使用過(guò)React進(jìn)行開(kāi)發(fā),并有一定經(jīng)驗(yàn)的人閱讀。
好了閑話少說(shuō),我們一起來(lái)看源碼吧
寫(xiě)過(guò)react知道,我們使用react編寫(xiě)代碼都離不開(kāi)webpack和babel,因?yàn)?b>React要求我們使用的是class定義組件,并且使用了JSX語(yǔ)法編寫(xiě)HTML。瀏覽器是不支持JSX并且對(duì)于class的支持也不好,所以我們都是需要使用webpack的jsx-loader對(duì)jsx的語(yǔ)法做一個(gè)轉(zhuǎn)換,并且對(duì)于ES6的語(yǔ)法和react的語(yǔ)法通過(guò)babel的babel/preset-react、babel/env和@babel/plugin-proposal-class-properties等進(jìn)行轉(zhuǎn)義。不熟悉怎么從頭搭建react的我的示例代碼就放在這。
好了,我們從一個(gè)最簡(jiǎn)單實(shí)例demo來(lái)看react到底做了什么
1、createElement下面是我們的代碼
import React from "react"; import ReactDOM from "react-dom"; ReactDOM.render(11111
, document.getElementById("root") );
這是頁(yè)面上的效果
我們現(xiàn)在看看在瀏覽器中的代碼是如何實(shí)現(xiàn)的:
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render(react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", { style: { color: "red" } }, "11111"), document.getElementById("root"));
最終經(jīng)過(guò)編譯后的代碼是這樣的,發(fā)現(xiàn)原本的11111
變成了一個(gè)react.createElement的函數(shù),其中原生標(biāo)簽的類型,內(nèi)容都變成了參數(shù)傳入這個(gè)函數(shù)中.這個(gè)時(shí)候我們大膽的猜測(cè)react.createElement接受三個(gè)參數(shù),分別是元素的類型、元素的屬性、子元素。好了帶著我們的猜想來(lái)看一下源碼。
我們不難找到,源碼位置在位置 ./node_modules/react/umd/react.development.js:1941
function createElement(type, config, children) { var propName = void 0; // Reserved names are extracted var props = {}; var key = null; var ref = null; var self = null; var source = null; if (config != null) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { key = "" + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object for (propName in config) { if (hasOwnProperty$1.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } // Children can be more than one argument, and those are transferred onto // the newly allocated props object. var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } { if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; } // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } { if (key || ref) { var displayName = typeof type === "function" ? type.displayName || type.name || "Unknown" : type; if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); }
首先我們來(lái)看一下它的三個(gè)參數(shù)
第一個(gè)type:我們想一下這個(gè)type的可能取值有哪些?
第一種就是我們上面寫(xiě)的原生的標(biāo)簽類型(例如h1、div,span等);
第二種就是我們React組件了,就是這面這種App
class App extends React.Component { static defaultProps = { text: "DEMO" } render() { return (222{this.props.text}
) } }
第二個(gè)config:這個(gè)就是我們傳遞的一些屬性
第三個(gè)children:這個(gè)就是子元素,最開(kāi)始我們猜想就三個(gè)參數(shù),其實(shí)后面看了源碼就知道這里其實(shí)不止三個(gè)。
接下來(lái)我們來(lái)看看react.createElement這個(gè)函數(shù)里面會(huì)幫我們做什么事情。
1、首先會(huì)初始化一些列的變量,之后會(huì)判斷我們傳入的元素中是否帶有有效的key和ref的屬性,這兩個(gè)屬性對(duì)于react是有特殊意義的(key是可以優(yōu)化React的渲染速度的,ref是可以獲取到React渲染后的真實(shí)DOM節(jié)點(diǎn)的),如果檢測(cè)到有傳入key,ref,__self和__source這4個(gè)屬性值,會(huì)將其保存起來(lái)。
2、接著對(duì)傳入的config做處理,遍歷config對(duì)象,并且剔除掉4個(gè)內(nèi)置的保留屬性(key,ref,__self,__source),之后重新組裝新的config為props。這個(gè)RESERVED_PROPS是定義保留屬性的地方。
var RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true };
3、之后會(huì)檢測(cè)傳入的參數(shù)的長(zhǎng)度,如果childrenLength等于1的情況下,那么就代表著當(dāng)前createElement的元素只有一個(gè)子元素,那么將內(nèi)容賦值到props.children。那什么時(shí)候childrenLength會(huì)大于1呢?那就是當(dāng)你的元素里面涉及到多個(gè)子元素的時(shí)候,那么children將會(huì)有多個(gè)傳入到createElement函數(shù)中。例如:
ReactDOM.render(, document.getElementById("root") );
111222
編譯后是什么樣呢?
react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", { style: { color: "red" }, key: "22" }, react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, "111"), react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("div", null, "222")), document.getElementById("root") );
這個(gè)時(shí)候react.createElement拿到的arguments.length就大于3了。也就是childrenLength大于1。這個(gè)時(shí)候我們就遍歷把這些子元素添加到props.children中。
4、接著函數(shù)將會(huì)檢測(cè)是否存在defaultProps這個(gè)參數(shù),因?yàn)楝F(xiàn)在的是一個(gè)最簡(jiǎn)單的demo,而且傳入的只是原生元素,所以沒(méi)有defaultProps這個(gè)參數(shù)。那么我們來(lái)看下面的例子:
import React, { Component } from "react"; import ReactDOM from "react-dom"; class App extends Component { static defaultProps = { text: "33333" } render() { return (222{this.props.text}
) } } ReactDOM.render(, document.getElementById("root") );
編譯后的
var App = /*#__PURE__*/ function (_Component) { _inherits(App, _Component); function App() { _classCallCheck(this, App); return _possibleConstructorReturn(this, _getPrototypeOf(App).apply(this, arguments)); } _createClass(App, [{ key: "render", value: function render() { return react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement("h1", null, "222", this.props.text); } }]); return App; }(react__WEBPACK_IMPORTED_MODULE_0__["Component"]); _defineProperty(App, "defaultProps", { text: "33333" }); react_dom__WEBPACK_IMPORTED_MODULE_1___default.a.render( react__WEBPACK_IMPORTED_MODULE_0___default.a.createElement(App, null), document.getElementById("root") );
發(fā)現(xiàn)傳入react.createElement的是一個(gè)App的函數(shù),class經(jīng)過(guò)babel轉(zhuǎn)換后會(huì)變成一個(gè)構(gòu)造函數(shù)。有興趣可以自己去看babel對(duì)于class的轉(zhuǎn)換,這里就不解析轉(zhuǎn)換過(guò)程,總得來(lái)說(shuō)就是返回一個(gè)App的構(gòu)造函數(shù)傳入到react.createElement中.如果type傳的東西是個(gè)對(duì)象,且type有defaultProps這個(gè)東西并且props中對(duì)應(yīng)的值是undefined,那就defaultProps的值也塞props里面。這就是我們組價(jià)默認(rèn)屬性的由來(lái)。
5、 檢測(cè)key和ref是否有賦值,如果有將會(huì)執(zhí)行defineKeyPropWarningGetter和defineRefPropWarningGetter兩個(gè)函數(shù)。
function defineKeyPropWarningGetter(props, displayName) { var warnAboutAccessingKey = function () { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true; warningWithoutStack$1(false, "%s: `key` is not a prop. Trying to access it will result " + "in `undefined` being returned. If you need to access the same " + "value within the child component, you should pass it as a different " + "prop. (https://fb.me/react-special-props)", displayName); } }; warnAboutAccessingKey.isReactWarning = true; Object.defineProperty(props, "key", { get: warnAboutAccessingKey, configurable: true }); } function defineRefPropWarningGetter(props, displayName) { var warnAboutAccessingRef = function () { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; warningWithoutStack$1(false, "%s: `ref` is not a prop. Trying to access it will result " + "in `undefined` being returned. If you need to access the same " + "value within the child component, you should pass it as a different " + "prop. (https://fb.me/react-special-props)", displayName); } }; warnAboutAccessingRef.isReactWarning = true; Object.defineProperty(props, "ref", { get: warnAboutAccessingRef, configurable: true }); }
我么可以看出這個(gè)二個(gè)方法就是給key和ref添加了警告。這個(gè)應(yīng)該只是在開(kāi)發(fā)環(huán)境才有其中isReactWarning就是上面判斷key與ref是否有效的一個(gè)標(biāo)記。
6、最后將一系列組裝好的數(shù)據(jù)傳入ReactElement函數(shù)中。
var ReactElement = function (type, key, ref, self, source, owner, props) { var element = { $$typeof: REACT_ELEMENT_TYPE, type: type, key: key, ref: ref, props: props, _owner: owner }; { element._store = {}; Object.defineProperty(element._store, "validated", { configurable: false, enumerable: false, writable: true, value: false }); Object.defineProperty(element, "_self", { configurable: false, enumerable: false, writable: false, value: self }); Object.defineProperty(element, "_source", { configurable: false, enumerable: false, writable: false, value: source }); if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); } } return element; };
其實(shí)里面非常簡(jiǎn)單,就是將傳進(jìn)來(lái)的值都包裝在一個(gè)element對(duì)象中
$$typeof:其中REACT_ELEMENT_TYPE是一個(gè)常量,用來(lái)標(biāo)識(shí)該對(duì)象是一個(gè)ReactElement
var hasSymbol = typeof Symbol === "function" && Symbol.for; var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for("react.element") : 0xeac7;
從代碼上看如果支持Symbol就會(huì)用Symbol.for方法創(chuàng)建一個(gè)key為react.element的symbol,否則就會(huì)返回一個(gè)0xeac7
type -> tagName或者是一個(gè)函數(shù)
key -> 渲染元素的key
ref -> 渲染元素的ref
props -> 渲染元素的props
_owner -> Record the component responsible for creating this element.(記錄負(fù)責(zé)創(chuàng)建此元素的組件,默認(rèn)為null)
_store -> 新的對(duì)象
_store中添加了一個(gè)新的對(duì)象validated(可寫(xiě)入),
element對(duì)象中添加了_self和_source屬性(只讀),最后凍結(jié)了element.props和element。
這樣就解釋了為什么我們?cè)谧咏M件內(nèi)修改props是沒(méi)有效果的,只有在父級(jí)修改了props后子組件才會(huì)生效
最后就將組裝好的element對(duì)象返回了出來(lái),提供給ReactDOM.render使用。到這有關(guān)的主要內(nèi)容我們看完了。下面我們來(lái)補(bǔ)充一下知識(shí)點(diǎn)
Object.freezeObject.freeze方法可以凍結(jié)一個(gè)對(duì)象,凍結(jié)指的是不能向這個(gè)對(duì)象添加新的屬性,不能修改其已有屬性的值,不能刪除已有屬性,以及不能修改該對(duì)象已有屬性的可枚舉性、可配置性、可寫(xiě)性。該方法返回被凍結(jié)的對(duì)象。
const obj = { a: 1, b: 2 }; Object.freeze(obj); obj.a = 3; // 修改無(wú)效
需要注意的是凍結(jié)中能凍結(jié)當(dāng)前對(duì)象的屬性,如果obj中有一個(gè)另外的對(duì)象,那么該對(duì)象還是可以修改的。所以React才會(huì)需要凍結(jié)element和element.props。
if (Object.freeze) { Object.freeze(element.props); Object.freeze(element); }
后續(xù)更多文章將在我的github第一時(shí)間發(fā)布,歡迎關(guān)注。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/103283.html
摘要:本篇開(kāi)始介紹自定義組件是如何渲染的。組件將自定義組件命名為,結(jié)構(gòu)如下經(jīng)過(guò)編譯后,生成如下代碼構(gòu)建頂層包裝組件跟普通元素渲染一樣,第一步先會(huì)執(zhí)行創(chuàng)建為的。調(diào)用順序已在代碼中注釋。先看圖,這部分內(nèi)容將在下回分解 前言 React 是一個(gè)十分龐大的庫(kù),由于要同時(shí)考慮 ReactDom 和 ReactNative ,還有服務(wù)器渲染等,導(dǎo)致其代碼抽象化程度很高,嵌套層級(jí)非常深,閱讀其源碼是一個(gè)非...
摘要:調(diào)用棧是這樣的這里生成的我們將其命名為,它將作為參數(shù)傳入到。整個(gè)的調(diào)用棧是這樣的組件間的層級(jí)結(jié)構(gòu)是這樣的到此為止,頂層對(duì)象已經(jīng)構(gòu)造完畢,下一步就是調(diào)用來(lái)自的方法,進(jìn)行頁(yè)面的渲染了。通過(guò)表達(dá)的結(jié)構(gòu)最終會(huì)轉(zhuǎn)化為一個(gè)純對(duì)象,用于下一步的渲染。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言...
摘要:楚江數(shù)據(jù)是專業(yè)的互聯(lián)網(wǎng)數(shù)據(jù)技術(shù)服務(wù),現(xiàn)整理出零基礎(chǔ)如何學(xué)爬蟲(chóng)技術(shù)以供學(xué)習(xí),。本文來(lái)源知乎作者路人甲鏈接楚江數(shù)據(jù)提供網(wǎng)站數(shù)據(jù)采集和爬蟲(chóng)軟件定制開(kāi)發(fā)服務(wù),服務(wù)范圍涵蓋社交網(wǎng)絡(luò)電子商務(wù)分類信息學(xué)術(shù)研究等。 楚江數(shù)據(jù)是專業(yè)的互聯(lián)網(wǎng)數(shù)據(jù)技術(shù)服務(wù),現(xiàn)整理出零基礎(chǔ)如何學(xué)爬蟲(chóng)技術(shù)以供學(xué)習(xí),http://www.chujiangdata.com。 第一:Python爬蟲(chóng)學(xué)習(xí)系列教程(來(lái)源于某博主:htt...
摘要:我們先來(lái)看下這個(gè)函數(shù)的一些神奇用法對(duì)于上述代碼,也就是函數(shù)來(lái)說(shuō)返回值是。不管你第二個(gè)參數(shù)的函數(shù)返回值是幾維嵌套數(shù)組,函數(shù)都能幫你攤平到一維數(shù)組,并且每次遍歷后返回的數(shù)組中的元素個(gè)數(shù)代表了同一個(gè)節(jié)點(diǎn)需要復(fù)制幾次。這是我的 React 源碼解讀課的第一篇文章,首先來(lái)說(shuō)說(shuō)為啥要寫(xiě)這個(gè)系列文章: 現(xiàn)在工作中基本都用 React 了,由此想了解下內(nèi)部原理 市面上 Vue 的源碼解讀數(shù)不勝數(shù),但是反觀...
摘要:僅針對(duì)數(shù)據(jù)屬性描述有效獲取該屬性的訪問(wèn)器函數(shù)。當(dāng)且僅當(dāng)指定對(duì)象的屬性可以被枚舉出時(shí),為。凍結(jié)及其對(duì)象主要目的是為提高測(cè)試環(huán)境下效率,將的一些屬性配置為不可枚舉,進(jìn)行遍歷的時(shí)候跳過(guò)這些屬性。 React系列 React系列 --- 簡(jiǎn)單模擬語(yǔ)法(一)React系列 --- Jsx, 合成事件與Refs(二)React系列 --- virtualdom diff算法實(shí)現(xiàn)分析(三)React...
閱讀 2980·2021-11-16 11:45
閱讀 5124·2021-09-22 10:57
閱讀 1763·2021-09-08 09:36
閱讀 1584·2021-09-02 15:40
閱讀 2508·2021-07-26 23:38
閱讀 1184·2019-08-30 15:55
閱讀 923·2019-08-30 15:54
閱讀 1213·2019-08-29 14:06