<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
本文適合0.5~3年的react開發人員的進階。
講講廢話:
react的原始碼,的確是比vue的難度要深一些,本文也是針對初中級,本意瞭解整個react的執行過程。
首先我們需要了解什麼是JSX。
網路大神的解釋:React 使用 JSX 來替代常規的 JavaScript。JSX 是一個看起來很像 XML 的 JavaScript 語法擴充套件。
是的,JSX是一種js的語法擴充套件,表面上像HTML,本質上還是通過babel轉換為js執行。再通俗的一點的說,jsx就是一段js,只是寫成了html的樣子,而我們讀取他的時候,jsx會自動轉換成vnode物件給我們,這裡都由react-script的內建的babel幫助我們完成。
簡單舉個栗子:
return ( <div> Hello Word </div> ) 實際上是: return React.createElement( "div", null, "Hello" )
JSX本質上就是轉換為React.createElement在React內部構建虛擬Dom,最終渲染出頁面。
這裡說明一下react的虛擬dom。react的虛擬dom跟vue的大為不同。vue的虛擬dom是為了是提高渲染效率,而react的虛擬dom是一定需要。很好理解,vue的template本身就是html,可以直接顯示。而jsx是js,需要轉換成html,所以用到虛擬dom。
我們描述一下react的最簡版的vnode:
function createElement(type, props, ...children) { props.children = children; return { type, props, children, }; }
這裡的vnode也很好理解,
type表示型別,如div,span,
props表示屬性,如{id: 1, style:{color:red}},
children表示子元素
下邊會在createElement繼續講解。
我們寫一個react的最簡單的原始碼:
import React from 'react' import ReactDOM from 'react-dom' function App(props){ return <div>你好</div> </div> } ReactDOM.render(<App/>, document.getElementById('root'))
React負責邏輯控制,資料 -> VDOM
首先,我們可以看到每一個js檔案中,都一定會引入import React from ‘react’。但是我們的程式碼裡邊,根本沒有用到React。但是你不引入他就報錯了。
為什麼呢?可以這樣理解,在我們上述的js檔案中,我們使用了jsx。但是jsx並不能給編譯,所以,報錯了。這時候,需要引入react,而react的作用,就是把jsx轉換為“虛擬dom”物件。
JSX本質上就是轉換為React.createElement在React內部構建虛擬Dom,最終渲染出頁面。而引入React,就是為了時限這個過程。
ReactDom渲染實際DOM,VDOM -> DOM
理解好這一步,我們再看ReactDOM。React將jsx轉換為“虛擬dom”物件。我們再利用ReactDom的虛擬dom通過render函數,轉換成dom。再通過插入到我們的真是頁面中。
這就是整個mini react的一個簡述過程。相關參考視訊講解:進入學習
react的功能化問題,暫時不考慮。例如,啟動react,怎麼去識別JSX,實現熱更新服務等等,我們的重點在於react自身。我們借用一下一下react-scripts外掛。
有幾種種方式建立我們的基本架子:
利用 create-react-app zwz_react_origin快速搭建,然後刪除原本的react,react-dom等檔案。(zwz_react_origin是我的專案名稱)
第二種,複製下邊程式碼。新建package.json
{ "name": "zwz_react_origin", "scripts": { "start": "react-scripts start" }, "version": "0.1.0", "private": true, "dependencies": { "react-scripts": "3.4.1" }, }
然後新建public下邊的index.html
<!DOCTYPE html> <html lang="en"> <head> </head> <body> <div id="root"></div> </body> </html>
再新建src下邊的index.js
這時候react-scripts會快速的幫我們定為到index.html以及引入index.js
import React from "react"; import ReactDOM from "react-dom"; let jsx = ( <div> <div className="">react啟動成功</div> </div> ); ReactDOM.render(jsx, document.getElementById("root"));
這樣,一個可以寫react原始碼的輪子就出來了。
let obj = ( <div> <div className="class_0">你好</div> </div> ); console.log(`obj=${ JSON.stringify( obj) }`);
首先,我們上述程式碼,如果我們不import React處理的話,我們可以列印出:
‘React’ must be in scope when using JSX react/react-in-jsx-scope
是的,編譯不下去,因為js檔案再react-script,他已經識別到obj是jsx。該jsx卻不能解析成虛擬dom, 此時我們的頁面就會報錯。通過資料的查閱,或者是原始碼的跟蹤,我們可以知道,實際上,識別到jsx之後,會呼叫頁面中的createElement轉換為虛擬dom。
我們import React,看看列印出來什麼?
+ import React from "react"; let obj = ( <div> <div className="class_0">你好</div> </div> ); console.log(`obj:${ JSON.stringify( obj) }`); 結果: jsx={"type":"div","key":null,"ref":null,"props":{"children":{"type":"div","key":null,"ref":null,"props":{"className":"class_0","children":"你好"},"_owner":null,"_store":{}}},"_owner":null,"_store":{}}
由上邊結論可以知道, babel會識別到我們的jsx,通過createElement並將其dom(html語法)轉換為虛擬dom。從上述的過程,我們可以看到虛擬dom的組成,由type,key,ref,props組成。我們來模擬react的原始碼。
此時我們已經知道react中的createElement的作用是什麼,我們可以嘗試著自己來寫一個createElement(新建react.js引入並手寫下邊程式碼):
function createElement() { console.log("createElement", arguments); } export default { createElement, };
此時的列印結果:
我們可以看出物件傳遞的時候,dom的格式,先傳入type, 然後props屬性,我們根據原本react模擬一下這個物件轉換的列印:
function createElement(type, props, ...children) { props.children = children; return { type, props, }; }
這樣,我們已經把最簡版的一個react實現,我們下邊繼續看看如何render到頁面
import React from "react"; + import ReactDOM from "react-dom"; let jsx = ( <div> <div className="class_0">你好</div> </div> ); // console.log(`jsx=${ JSON.stringify( jsx) }`); + ReactDOM.render(jsx, document.getElementById("root"));
如果此時,我們引入ReactDom,通過render到對應的元素,整個簡版react的就已經完成,頁面就會完成渲染。首先,jsx我們已經知道是一個vnode,而第二個元素即是渲染上頁面的元素,假設我們的元素是一個html原生標籤div。
我們新建一個reactDom.js引入。
function render(vnode, container) { mount(vnode, container); } function mount(vnode, container){ const { type, props } = vnode; const node = document.createElement(type);//建立一個真實dom const { children, ...rest } = props; children.map(item => {//子元素遞迴 if (Array.isArray(item)) { item.map(c => { mount(c, node); }); } else { mount(item, node); } }); container.appendChild(node); } //主頁: - import React from "react"; - import ReactDOM from "react-dom"; + import React from "./myReact/index.js"; + import ReactDOM from "./myReact/reactDom.js"; let jsx = ( <div> <div className="class_0">你好</div> </div> ); ReactDOM.render(jsx, document.getElementById("root"));
此時,我們可以看到頁面,我們自己寫的一個react渲染已經完成。我們優化一下。
首先,這個過程中, className="class_0"消失了。我們想辦法渲染上頁面。此時,虛擬dom的物件,沒有辦法,區分,哪些元素分別帶有什麼屬性,我們在跳脫的時候優化一下mount。
function mount(vnode, container){ const { type, props } = vnode; const node = document.createElement(type);//建立一個真實dom const { children, ...rest } = props; children.map(item => {//子元素遞迴 if (Array.isArray(item)) { item.map(c => { mount(c, node); }); } else { mount(item, node); } }); // +開始 Object.keys(rest).map(item => { if (item === "className") { node.setAttribute("class", rest[item]); } if (item.slice(0, 2) === "on") { node.addEventListener("click", rest[item]); } }); // +結束 container.appendChild(node); }
看到這裡,整個字串render到頁面渲染的過程已完成。此時入口檔案已經解決了。對於原始標籤div, h1已經相容。但是對於自定義標籤呢?或者怎麼完成元件化呢。
我們先看react16+的兩種元件化模式,一種是function元件化,一種是class元件化。
首先,我們先看看demo.
import React, { Component } from "react"; import ReactDOM from "react-dom"; class MyClassCmp extends React.Component { constructor(props) { super(props); } render() { return ( <div className="class_2" >MyClassCmp表示:{this.props.name}</div> ); } } function MyFuncCmp(props) { return <div className="class_1" >MyFuncCmp表示:{props.name}</div>; } let jsx = ( <div> <h1>你好</h1> <div className="class_0">前端小夥子</div> <MyFuncCmp /> <MyClassCmp /> </div> ); ReactDOM.render(jsx, document.getElementById("root"));
先看簡單點一些的Function元件。暫不考慮傳遞值等問題,Function其實跟原本元件不一樣的地方,在於他是個函數,而原本的jsx,是一個字串。我們可以根據這個特點,將函數轉換為字串,那麼Function元件即跟普通標籤同一性質。
我們寫一個方法:
mountFunc(vnode, container); function mountFunc(vnode, container) { const { type, props } = vnode; const node = new type(props); mount(node, container); }
此時type即是函數體內容,我們只需要範例化一下,即可跟拿到對應的字串,即是普通的vnode。再利用我們原來的vnode轉換方法,即可實現。
按照這個思路,如果我們不考慮生命週期等相對複雜的東西。我們也相對簡單,只需拿到類中的render函數即可。
mountFunc(vnode, container); function mountClass(vnode, container) { const { type, props } = vnode; const node = new type(props); mount(node.render(), container); }
這裡可能需注意,class元件,需要繼承React.Component。截圖一下react自帶的Component
可以看到,Component統一封裝了,setState,forceUpdate方法,記錄了props,state,refs等。我們模擬一份簡版為栗子:
class Component { static isReactComponent = true; constructor(props) { this.props = props; this.state = {}; } setState = () => {}; }
再新增一個標識,isReactComponent表示是函數陣列件化。這樣的話,我們就可以區分出:普通標籤,函陣列件標籤,類元件標籤。
我們可以重構一下createElement方法,多定義一個vtype屬性,分別表示
根據上述標記,我們可改造為:
function createElement(type, props, ...children) { props.children = children; let vtype; if (typeof type === "string") { vtype = 1; } if (typeof type === "function") { vtype = type.isReactComponent ? 2 : 3; } return { vtype, type, props, };
那麼,我們處理時:
function mount(vnode, container) { const { vtype } = vnode; if (vtype === 1) { mountHtml(vnode, container); //處理原生標籤 } if (vtype === 2) { //處理class元件 mountClass(vnode, container); } if (vtype === 3) { //處理常式元件 mountFunc(vnode, container); } }
至此,我們已經完成一個簡單可元件化的react原始碼。不過,此時有個bug,就是文字元素的時候異常,因為文字元素不帶標籤。我們優化一下。
function mount(vnode, container) { const { vtype } = vnode; if (!vtype) { mountTextNode(vnode, container); //處理文位元組點 } //vtype === 1 //vtype === 2 // .... } //處理文位元組點 function mountTextNode(vnode, container) { const node = document.createTextNode(vnode); container.appendChild(node); }
package.json:
{ "name": "zwz_react_origin", "version": "0.1.0", "private": true, "dependencies": { "react": "^16.10.2", "react-dom": "^16.10.2", "react-scripts": "3.2.0" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": "react-app" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }}
index.js
import React from "./wzReact/"; import ReactDOM from "./wzReact/ReactDOM"; class MyClassCmp extends React.Component { constructor(props) { super(props); } render() { return ( <div className="class_2" >MyClassCmp表示:{this.props.name}</div> ); } } function MyFuncCmp(props) { return <div className="class_1" >MyFuncCmp表示:{props.name}</div>; } let jsx = ( <div> <h1>你好</h1> <div className="class_0">前端小夥子</div> <MyFuncCmp name="真帥" /> <MyClassCmp name="還有錢" /> </div> ); ReactDOM.render(jsx, document.getElementById("root"));
/wzReact/index.js
function createElement(type, props, ...children) { console.log("createElement", arguments); props.children = children; let vtype; if (typeof type === "string") { vtype = 1; } if (typeof type === "function") { vtype = type.isReactComponent ? 2 : 3; } return { vtype, type, props, }; } class Component { static isReactComponent = true; constructor(props) { this.props = props; this.state = {}; } setState = () => {}; } export default { Component, createElement, };
/wzReact/ReactDOM.js
function render(vnode, container) { console.log("render", vnode); //vnode-> node mount(vnode, container); // container.appendChild(node) } // vnode-> node function mount(vnode, container) { const { vtype } = vnode; if (!vtype) { mountTextNode(vnode, container); //處理文位元組點 } if (vtype === 1) { mountHtml(vnode, container); //處理原生標籤 } if (vtype === 3) { //處理常式元件 mountFunc(vnode, container); } if (vtype === 2) { //處理class元件 mountClass(vnode, container); } } //處理文位元組點 function mountTextNode(vnode, container) { const node = document.createTextNode(vnode); container.appendChild(node); } //處理原生標籤 function mountHtml(vnode, container) { const { type, props } = vnode; const node = document.createElement(type); const { children, ...rest } = props; children.map(item => { if (Array.isArray(item)) { item.map(c => { mount(c, node); }); } else { mount(item, node); } }); Object.keys(rest).map(item => { if (item === "className") { node.setAttribute("class", rest[item]); } if (item.slice(0, 2) === "on") { node.addEventListener("click", rest[item]); } }); container.appendChild(node); } function mountFunc(vnode, container) { const { type, props } = vnode; const node = new type(props); mount(node, container); } function mountClass(vnode, container) { const { type, props } = vnode; const cmp = new type(props); const node = cmp.render(); mount(node, container); } export default { render, };
到此這篇關於React執行機制超詳細講解的文章就介紹到這了,更多相關React執行機制內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45