不到一百行代碼,咱們來實現一個簡簡簡簡簡簡簡簡簡簡版react庫

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是對JS的一個拓展。JSX將HTMl語法直接加入到JS代碼中,再經過編譯器(Babel-loader)轉換成純JS給瀏覽器執行。
  • JSX只要用於構建React的View層,類HTML語法,學習成本幾乎爲零。
  • JSX相對於直接用React.createElement更快,更容易維護,更簡潔。

臥槽,怎麼說到了JSX裏面去了,成吉思汗你真不是人,當大佬們什麼都不懂啊。npm

React.createElement又是什麼?它能作什麼?又是怎樣作的???數組

  • React.createElement是react裏面提供的一個核心方法,它能夠將JSX轉換成VDMO;說是核心方法,但這個放法實現很是簡單,就是返回了一個JS對象。

ReactDom.render又是什麼?它能作什麼?又是怎樣作的???瀏覽器

  • ReactDom.render是ReactDom提供的一個核心方法;ReactDom是FB團隊故意從react中抽離出來的,但它只能渲染在瀏覽器。ReactDom.render能夠將React.createElement轉換成的VDOM,變成真實DOM掛載在瀏覽器上。

好了,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)

小弟,第一次寫文章,求大佬輕噴,有不足的地方,但願大佬們指出來。

相關文章
相關標籤/搜索