good evening everybody!這是一篇關於react故事的文章,這個故事主要是講在一個夜黑風高晚上,react從一個VDOM變成真實DOM的過程。node
這個過程react經歷了從JSX->React.createElement->VDOM->ReactDom.render->真實DOM的大致一個流程。react
使用過react的開發者都知道,react一開編寫的就是JSX。es6
以上這段看上去是HTML,但其實它是JSX。什麼是JSX?爲何用JSX?它有什麼好?算法
臥槽,怎麼說到了JSX裏面去了,成吉思汗你真不是人,當大佬們什麼都不懂啊。npm
React.createElement又是什麼?它能作什麼?又是怎樣作的???數組
ReactDom.render又是什麼?它能作什麼?又是怎樣作的???瀏覽器
好了,react從VDOM到真實DOM的轉換流程概念大概就是這樣,如今經過手寫React.createElement,React.Component,ReactDom.render三個API(這裏實現的三個API,只是負責這個流程的實現,不帶任何屬性(class,key,ref...)的處理,和diff算法(tree diff,component diff,element diff)之類的東西,是三個極簡版的API)bash
首先,咱們經過create-react-app建立一個工程,而後將其餘文件刪掉,只留下index.jsapp
在src文件在建立一個文件夾(custom)存放本身實現的react(customReact),和react-dom(coutomReactDom)。dom
index.js
import React from './custom/customReact';
import ReactDOM from './custom/customReactDom';
let arr = ['su', 'yue', 'feng']
function Container(props) {
return <div>{props.name}</div>
}
class Test extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div>
{this.props.name}
<ul>
{
arr.map(item => <li key={item}>{item}</li>)
}
</ul>
</div>
}
}
const JSX = (
<div>
<Container name="函數組件" />
<span>普通文本</span>
<Test name="類組件" />
</div>
)
ReactDOM.render(JSX, document.getElementById('root'));
複製代碼
以上index.js文件咱們寫上兩個組件,(一個函數組件也叫無狀態組件,之從hook出來以後,就不能再叫無狀態組件了;一個類組件),還有一些普通標籤組件。構成reactVDOM無非就這幾種類型。
當咱們打包編譯的時候,以上的JSX就會被React.createElement轉換成VDOM;因此咱們在customReact中起碼要實現一個createElement方法;若是使用了類組件,還有實現一個Component方法。
function createElement(type, props, ...children) {
let vtype
props.children = children
if (typeof type === 'string') {
vtype = 1
} else if (typeof type === 'function') {
if (type.prototype.render) {
vtype = 2
} else {
vtype = 3
}
}
return { vtype, type, props }
}
class Component {
constructor(props) {
this.props = props
this.state = {}
this.refs = {}
}
setState() { }
forceUpdate(){}
}
export default { createElement, Component }
複製代碼
createElement方法接受三個參數,type(標籤類型),prop(屬性),children(子標籤,子屬性,這有多是多個,因此經過es6展開符...,將它們搞成一個數組)。
在createElement裏面咱們經過type的類型判斷他是普通標籤,仍是組件。若是type是string類型的話,就是普通標籤,咱們用1表示;若是type是function類型的話,就是組件,可是組件又分函數組件,和類組件,這兩種組件,咱們用render方法區分,若是又render方法的就是類組件,咱們用2表示,沒有render方法的就是函數組件,咱們用3表示。最後返回一個JS對象。
在Component方法裏面,咱們什麼也不作就是初始換一個變量,setState(),forceUpdate()這兩個方法應該你們都不陌生。這裏就不去實現,由於這部分的內容太多,涉及到diff,執行機制,更新機制等等,有興趣的本身去看源碼。
最後咱們導出這兩個方法。
在打包編譯的時候,當JSX就會被React.createElement轉換成VDOM後,就會執行,react-dom的render方法,這個放法會將VDMO變成真實DOM,因此咱們在實現本身的react-dom的時候起碼要有一個render方法。
coutomReactDom.js
function render(vnode, container) {
container.appendChild(initVnode(vnode))
}
function initVnode(vnode) {
if (!vnode.vtype) {
return document.createTextNode(vnode)
}
if (vnode.vtype === 1) {
return createElement(vnode)
} else if (vnode.vtype === 2) {
return createClassCom(vnode)
} else if (vnode.vtype === 3) {
return createFunctionCom(vnode)
}
}
function createElement(vnode) {
let { type, props: { key, children } } = vnode
let node = document.createElement(type)
children.forEach(element => {
if (Array.isArray(element)) {
element.forEach(c => {
node.appendChild(initVnode(c))
})
} else {
node.appendChild(initVnode(element))
}
});
return node
}
function createClassCom(vnode) {
let { type, props } = vnode
let instance = new type(props)
return initVnode(instance.render())
}
function createFunctionCom(vnode) {
let { type, props } = vnode
return initVnode(type(props))
}
export default { render }
複製代碼
render方法接收兩個參數,vnode(虛擬DOM),container(掛載節點)
在render裏面,咱們經過傳進來的虛擬DOM的vtype(也就是咱們剛纔經過1,2,3,標記的類型)來建立真實的DOM。若是vnode沒有vtype的話,咱們就經過document.createTextNode(vnode)來建立該文本,若是是1的話,咱們就經過createElement方法來建立,createElement方法,只要是經過document.createElement()和遞歸的方式實現建立節點的流程。若是是2的話,咱們就經過createClassCom()方法去建立節點;若是是3的話就經過createFunctionCom()方法去建立節點。這兩個方法及其簡單加起來就五行代碼,這兩個方法只要是去拿到組件的虛擬節點,而後給initVnode方法就看能夠。由於不管什麼組件,到最後仍是跑回到建立普通標籤的流程,也就是最後仍是回到createElement方法裏面。
好了,這樣就大功告成了,這樣就實現了本身的一個react庫,倆個文件加起來也只有70行代碼。(真正的react裏面的作的東西,處理的東西會比這個複雜上千萬賠,這裏只不過是實現是它從VDOM到真實DOM的轉換大概流程)
這是跑起來的效果(npm start)
小弟,第一次寫文章,求大佬輕噴,有不足的地方,但願大佬們指出來。