摘要:內(nèi)部機(jī)制探秘和文末附彩蛋和源碼這篇文章比較偏基礎(chǔ),但是對(duì)入門內(nèi)部機(jī)制和實(shí)現(xiàn)原理卻至關(guān)重要。當(dāng)然也需要明白一些淺顯的內(nèi)部工作機(jī)制。當(dāng)改變出現(xiàn)時(shí),相比于真實(shí)更新虛擬的性能優(yōu)勢(shì)非常明顯。直到最終,會(huì)得到完整的表述樹的對(duì)象。
React 內(nèi)部機(jī)制探秘 - React Component 和 Element(文末附彩蛋demo和源碼)
這篇文章比較偏基礎(chǔ),但是對(duì)入門 React 內(nèi)部機(jī)制和實(shí)現(xiàn)原理卻至關(guān)重要。算是為以后深入解讀的一個(gè)入門,如果您已經(jīng)非常清楚:
React Component Render => JSX => React.createElement => Virtual Dom
的流程,可以直接略過(guò)此文。
谷歌工程師一個(gè)風(fēng)騷的問(wèn)題在幾個(gè)月前,谷歌的前端開發(fā)專家 Tyler McGinnis 在其個(gè)人 twitter 賬號(hào)上發(fā)布了 這樣一條推文,引發(fā)了對(duì) React 組件的討論。
他拋出來(lái)的問(wèn)題是 :如上述代碼,React 組件 Icon 直接出現(xiàn)在代碼中,到底算什么?
提供的選項(xiàng)有:
A. Component Declaration 組件聲明
B. Component Invocation 組件調(diào)用
C. Component Instantiation 組件實(shí)例化
D. Using a Component 單純地使用組件
有趣的是,參與回答的開發(fā)者中:
有 15% 選擇了 A 項(xiàng);
有 8% 選擇了 B 項(xiàng);
有 45% 選擇了 C 項(xiàng);
有 32% 選擇了 D 項(xiàng);
對(duì) React 開發(fā)經(jīng)驗(yàn)豐富的前端工程師來(lái)說(shuō),這個(gè)問(wèn)題其實(shí)很好理解。它的關(guān)鍵在于:真正明白 React Element 和 React Components,以及 JSX 抽象層是如連通 React 的。當(dāng)然也需要明白一些淺顯的 React 內(nèi)部工作機(jī)制。
這篇文章,就帶領(lǐng)大家研究一下這個(gè) JSX 抽象層的奧秘和 React Reconciliation 過(guò)程。
React 和 React Element 到底是什么?讓我們回到最初,思考一下最原始的問(wèn)題,React 到底是什么?
簡(jiǎn)而言之,
React is a library for building user interfaces.
React 是一個(gè)構(gòu)建視圖層的類庫(kù)(框架...whatever...)。不管 React 本身如何復(fù)雜,不管其生態(tài)如何龐大,構(gòu)建視圖始終是他的核心。記住這個(gè)信息,我們即將進(jìn)入今天的第一個(gè)概念 — React Element。
簡(jiǎn)單地說(shuō),React Element 描述了“你想”在屏幕上看到的事物。
抽象地說(shuō),React Element 元素是一個(gè)描述了 Dom Node 的對(duì)象。
請(qǐng)注意我的用詞 — “描述”,因?yàn)?React Element 并不是你在屏幕上看見的真實(shí)事物。相反地,他是一個(gè)描述真實(shí)事物的集合。存在的就是合理的,我們看來(lái)看看 React Element 存在的意義,以及為什么會(huì)有這樣一個(gè)概念:
JavaScript 對(duì)象很輕量。用對(duì)象來(lái)作為 React Element,那么 React 可以輕松的創(chuàng)建或銷毀這些元素,而不必去太擔(dān)心操作成本;
React 具有分析這些對(duì)象的能力,進(jìn)一步,也具有分析虛擬 Dom 的能力。當(dāng)改變出現(xiàn)時(shí),(相比于真實(shí) Dom)更新虛擬 Dom 的性能優(yōu)勢(shì)非常明顯。
為了創(chuàng)建我們描述 Dom Node 的對(duì)象(或者 React Element),我們可以使用 React.createElement 方法:
const element = React.createElement( "div", {id: "login-btn"}, "Login" )
這里 React.createElement 方法接受三個(gè)參數(shù):
一個(gè)表述標(biāo)簽名稱的字符串 (div, span, etc.);
當(dāng)前 React Element 需要具有的屬性;
當(dāng)前 React Element 要表達(dá)的內(nèi)容,或者一個(gè)子元素。
上面 React.createElement 方法調(diào)用之后,會(huì)返回一個(gè) javascript 對(duì)象:
{ type: "div", props: { children: "Login", id: "login-btn" } }
接著當(dāng)我們使用 ReactDOM.render 方法,這才渲染到真實(shí) DOM 之上時(shí),就會(huì)得到:
Login
而這個(gè)才是真實(shí)的 Dom 節(jié)點(diǎn)。
到目前為止,并沒有什么很難理解的概念。
React Element 深入和 React Component這篇文章我們開篇就介紹了 React Element,而并不是像官網(wǎng)或者學(xué)習(xí)資料上來(lái)就介紹 React Component,我相信你理解了 React Element,理解 React Component 就是自然而然的事情了。
在真正開發(fā)時(shí),我們并不直接使用 React.createElement,這樣做簡(jiǎn)直太無(wú)聊了,每個(gè)組件都這樣寫一定會(huì)瘋掉的。這時(shí)候就出現(xiàn)了 React Component,即 React 組件。
A component is a function or a Class which optionally accepts input and returns a React element.
沒錯(cuò),組件就是一個(gè)函數(shù)或者一個(gè) Class(當(dāng)然 Class 也是 function),它根據(jù)輸入?yún)?shù),并最終返回一個(gè) React Element,而不需要我們直接手寫無(wú)聊的 React Element。
所以說(shuō),實(shí)際上我們使用了 React Component 來(lái)生成 React Element,這對(duì)于開發(fā)體驗(yàn)的提升無(wú)疑是巨大的。
這里剖出一個(gè)思考題:所有 React Component 都需要返回 React Element 嗎?顯然是不需要的,那么 return null; 的 React 組件有存在的意義嗎,它能完成并實(shí)現(xiàn)哪些巧妙的設(shè)計(jì)和思想?(請(qǐng)關(guān)注作者,下篇文章將會(huì)專門進(jìn)行分析、講解)
從場(chǎng)景實(shí)例來(lái)看問(wèn)題接下來(lái),請(qǐng)看這樣一段代碼:
function Button ({ onLogin }) { return React.createElement( "div", {id: "login-btn", onClick: onLogin}, "Login" ) }
我們定義了一個(gè) Button 組件,它接收 onLogin 參數(shù),并返回一個(gè) React Element。注意 onLogin 參數(shù)是一個(gè)函數(shù),并最終像 id:"login-btn" 一樣成為了這個(gè) React Element 的屬性。
直到目前,我們見到了一個(gè) React Element type 為 HTML 標(biāo)簽(“span”, “div”, etc)的情況。事實(shí)上,我們也可以傳遞另一個(gè) React Element :
const element = React.createElement( User, {name: "Lucas"}, null )
注意此時(shí) React.createElement 第一個(gè)參數(shù)是另一個(gè) React Element,這與 type 值為 HTML 標(biāo)簽的情況不盡相同,當(dāng) React 發(fā)現(xiàn) type 值為一個(gè) class 或者函數(shù)時(shí),它就會(huì)先看這個(gè) class 或函數(shù)會(huì)返回什么樣的 Element,并為這個(gè) Element 設(shè)置正確的屬性。
React 會(huì)一直不斷重復(fù)這個(gè)過(guò)程(有點(diǎn)類似遞歸),直到?jīng)]有 “createElement 調(diào)用 type 值為 class 或者 function” 的情況。
我們結(jié)合代碼再來(lái)體會(huì)一下:
function Button ({ addFriend }) { return React.createElement( "button", { onClick: addFriend }, "Add Friend" ) } function User({ name, addFriend }) { return React.createElement( "div", null, React.createElement( "p", null, name ), React.createElement(Button, { addFriend }) ) }
上面有兩個(gè)組件:Button 和 User,User 描述的 Dom 是一個(gè) div 標(biāo)簽,這個(gè) div 內(nèi),又存在一個(gè) p 標(biāo)簽,這個(gè) p 標(biāo)簽展示了用戶的 name;還存在一個(gè) Button。
現(xiàn)在我們來(lái)看 User 和 Button 中,React.createElement 返回情況:
function Button ({ addFriend }) { return { type: "button", props: { onClick: addFriend, children: "Add Friend" } } } function User ({ name, addFriend }) { return { type: "div", props: { children: [{ type: "p", props: { children: name } }, { type: Button, props: { addFriend } }] } } }
你會(huì)發(fā)現(xiàn),上面的輸出中,我們發(fā)現(xiàn)了四種 type 值:
"button";
"div";
"p";
Button
當(dāng) React 發(fā)現(xiàn) type 是 Button 時(shí),它會(huì)查詢這個(gè) Button 組件會(huì)返回什么樣的 React Element,并賦予正確的 props。
直到最終,React 會(huì)得到完整的表述 Dom 樹的對(duì)象。在我們的例子中,就是:
{ type: "div", props: { children: [{ type: "p", props: { children: "Tyler McGinnis" } }, { type: "button", props: { onClick: addFriend, children: "Add Friend" } }] } }
React 處理這些邏輯的過(guò)程就叫做 reconciliation,那么“這個(gè)過(guò)程(reconciliation)在何時(shí)被觸發(fā)呢?”
答案當(dāng)然就是每次 setState 或 ReactDOM.render 調(diào)用時(shí)。以后的分析文章將會(huì)更加詳細(xì)的說(shuō)明。
好吧,再回到 Tyler McGinnis 那個(gè)風(fēng)騷的問(wèn)題上。
此時(shí)我們具備回答這個(gè)問(wèn)題的一切知識(shí)了嗎?稍等等,我要引出 JSX 這個(gè)老朋友了。
JSX 的角色在 React Component 編寫時(shí),相信大家都在使用 JSX 來(lái)描述虛擬 Dom。當(dāng)然,反過(guò)來(lái)說(shuō),React 其實(shí)也可以脫離 JSX 而存在。
文章開頭部分,我提到 “不常被我們提起的 JSX 抽象層是如何聯(lián)通 React 的?” 答案很簡(jiǎn)單,因?yàn)?JSX 總是被編譯成為 React.createElement 而被調(diào)用。一般 Babel 為我們做了 JSX —> React.createElement 這件事情。
再看來(lái)先例:
function Button ({ addFriend }) { return React.createElement( "button", { onClick: addFriend }, "Add Friend" ) } function User({ name, addFriend }) { return React.createElement( "div", null, React.createElement( "p", null, name), React.createElement(Button, { addFriend }) ) }
對(duì)應(yīng)我們總在寫的 JSX 用法:
function Button ({ addFriend }) { return ( ) } function User ({ name, addFriend }) { return () }{name}
就是一個(gè)編譯產(chǎn)出的差別。
最終答案和文末彩蛋那么,請(qǐng)你來(lái)回答“Icon 組件多帶帶出現(xiàn)代表了什么?”
Icon 在 JSX 被編譯之后,就有:
React.createElement(Icon, null)
你問(wèn)我怎么知道這些編譯結(jié)果的?
或者
你想知道你編寫的 JSX 最終編譯成了什么樣子?
我寫了一個(gè)小工具,進(jìn)行對(duì) JSX 的實(shí)時(shí)編譯,放在 Github倉(cāng)庫(kù)中,它使用起來(lái)是這樣子的:
平臺(tái)一分為二,左邊可以寫 JSX,右邊實(shí)時(shí)展現(xiàn)其編譯結(jié)果:
以及:
這個(gè)工具最核心的代碼其實(shí)就是使用 babel 進(jìn)行編譯:
let code = e.target.value; try { this.setState({ output: window.Babel.transform(code, {presets: ["es2015", "react"]}) .code, err: "" }) } catch(err) { this.setState({err: err.message}) }
感興趣的讀者可以去 GitHub 倉(cāng)庫(kù)參看源碼。
總結(jié)其實(shí)不管是 JSX 還是 React Element、React Component 這些概念,都是大家在開發(fā)中天天接觸到的。有的開發(fā)者也許能上手做項(xiàng)目,但是并沒有深入理解其中的概念,更無(wú)法真正掌握 React 核心思想。
這些內(nèi)容其實(shí)比較基礎(chǔ),但同時(shí)又很關(guān)鍵,對(duì)于后續(xù)理解 React/Preact 源碼至關(guān)重要。在這個(gè)基礎(chǔ)上,我會(huì)更新更多更加深入的類 React 實(shí)現(xiàn)原理剖析,感興趣的讀者可以關(guān)注。
我的其他幾篇關(guān)于React技術(shù)棧的文章:
通過(guò)實(shí)例,學(xué)習(xí)編寫 React 組件的“最佳實(shí)踐”
從 React 綁定 this,看 JS 語(yǔ)言發(fā)展和框架設(shè)計(jì)
做出Uber移動(dòng)網(wǎng)頁(yè)版還不夠 極致性能打造才見真章
解析Twitter前端架構(gòu) 學(xué)習(xí)復(fù)雜場(chǎng)景數(shù)據(jù)設(shè)計(jì)
React Conf 2017 干貨總結(jié)1: React + ES next = ?
React+Redux打造“NEWS EARLY”單頁(yè)應(yīng)用 一個(gè)項(xiàng)目理解最前沿技術(shù)棧真諦
一個(gè)react+redux工程實(shí)例
......
Happy Coding!
PS:
作者Github倉(cāng)庫(kù) 和 知乎問(wèn)答鏈接
歡迎各種形式交流。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/88774.html
摘要:前端日?qǐng)?bào)精選劉海打理指北中的錯(cuò)誤處理模式與反模式譯圖解和譯你并不知道中文裝飾器讓你的代碼更簡(jiǎn)潔眾成翻譯第期每個(gè)程序員第一份工作前應(yīng)該知道的件事中的不變性眾成翻譯寫的一次小結(jié)掘金內(nèi)部機(jī)制探秘和文末附彩蛋和源碼前端雜談開發(fā)實(shí)戰(zhàn) 2017-09-30 前端日?qǐng)?bào) 精選 iPhone X 劉海打理指北React16中的錯(cuò)誤處理ES6 Promise:模式與反模式「譯」圖解 ArrayBuffer...
摘要:現(xiàn)在關(guān)于最新版本新特性的宣傳講解已經(jīng)鋪天蓋地了。測(cè)試場(chǎng)景是反復(fù)操作數(shù)組,這個(gè)反復(fù)操作有所講究,我們計(jì)劃持續(xù)不斷地改變數(shù)組的某一項(xiàng)而不是整個(gè)數(shù)組的大范圍變動(dòng)。代碼和性能測(cè)試在使用開發(fā)時(shí),相信很多開發(fā)者在搭配函數(shù)式的狀態(tài)管理框架使用。 現(xiàn)在關(guān)于 React 最新 v16 版本新特性的宣傳、講解已經(jīng)鋪天蓋地了。你最喜歡哪一個(gè) new feature?截至目前,組件構(gòu)建方式已經(jīng)琳瑯滿目。那么,...
摘要:中的常見寫法先看下這段代碼。聲明式編程,就是告訴機(jī)器你想要的是什么,讓機(jī)器想出如何去做。最獨(dú)特的特性之一,是其非侵入性的響應(yīng)式系統(tǒng)。的縮寫將遍歷此對(duì)象所有的屬性。這一過(guò)程被稱為依賴收集。組件的顯示,數(shù)據(jù)的體現(xiàn)大部分都是由承載,傳遞。 目錄 緣起 Android開發(fā)中的常見寫法 JQuery中的常見寫法 命令式編程 聲明式編程 React中的常見寫法 Vue的常見寫法 你肯定熟悉響應(yīng)...
showImg(https://segmentfault.com/img/bVbvOmp?w=1612&h=888); 隨著React Vue前端框架的興起,出現(xiàn)了Vue-router,react-router-dom等前端路由管理庫(kù),利用他們構(gòu)建出來(lái)的單頁(yè)面應(yīng)用,也是越來(lái)越接近原生的體驗(yàn),再也不是以前的點(diǎn)擊標(biāo)簽跳轉(zhuǎn)頁(yè)面,刷新整個(gè)頁(yè)面了,那么他們的原理是什么呢? 優(yōu)質(zhì)gitHub開源練手項(xiàng)目: ...
閱讀 3258·2023-04-25 22:47
閱讀 3771·2021-10-11 10:59
閱讀 2304·2021-09-07 10:12
閱讀 4252·2021-08-11 11:15
閱讀 3434·2019-08-30 13:15
閱讀 1753·2019-08-30 13:00
閱讀 973·2019-08-29 14:02
閱讀 1688·2019-08-26 13:57