假期無聊,不如寫寫簡單版React

各位小夥伴,春節假期已過大半,這個春節應該是歷年來過的最安靜的一個春節了吧,每天家裏蹲,吃了睡,睡了吃,沒想到不出門居然成了對社會最大的貢獻。廢話很少說,開始說正題。css

相信不少人都用過react開發項目,也有不少人好奇頁面中明明沒有直接用到React,可是頁面卻必須引入React,不然就會報錯。初學者必定有這個疑問,不要着急,今天我就爲你解答這個疑問。html

JSX

jsx是個啥?

jsx其實就是個語法糖,用寫html的方式來寫js,一個很像xml的js擴展。React使用jsx來替代常規的js。在線體驗node

爲何要引入jsx?

寫js不香嗎?爲何要引入一個新概念jsx來迷惑你們?歸納來說jsx來說有如下幾個好處:react

  • 提高開發效率。使用jsx來寫模版速度嗖嗖的。
  • 提高執行效率。jsx編譯爲js代碼後進行來不少優化,執⾏更快。
  • 類型安全。在編譯過程當中就能發現錯誤。

嘗試過上面的在線體驗後,就會發現實際上babel-loader會把jsx預編譯爲React.createElement(xxx)。
jsx預處理前:webpack

jsx預處理後git

經過以上兩張圖片對比能夠解答文章開頭的疑問,爲何React沒有使用,卻必須引入?其實並非沒有使用,只不過沒有直接使用,須要通過babel轉化。另外能夠看出React幾個核心API:React.createElement, React.Component, ReactDom.render。

接下來咱們就本身動手來實現這三個API吧!github

核心API實現(簡版)

createElement和Component

做用:將傳入的節點轉化成vdom。
(1)建立./simple-react/component.js。實現class組件必備條件。web

export class Component{
    static isReactComponent={};
    constructor(props){
        this.props=props;
        this.state={}
    }
}
複製代碼

(2)建立./simple-react/index.js文件數組

import {Component} from "./component"

function createElement(type,props,...children){
    props.children=children;
    // console.log(type);
    // 判斷組件類型
    let vtype;
    if(typeof type==="string"){
        // 原生標籤
        vtype=1;
    }else if(typeof type === "function"){
        // 類組件,函數式組件
        vtype=type.isReactComponent ? 3 : 2;
    }
    return {
        vtype,
        type,
        props
    }
}

const React={
    createElement,
    Component
}

export default React;
複製代碼

上面我只是簡單使用了一、二、3來分別標示來標籤類型,你也可使用別的方式來處理。createElement被調用時會傳入標籤類型type,標籤屬性props及若⼲子元素children。安全

render

做用:渲染vdom,掛載到真實dom樹上。
建立./simple-react/ReactDOM.js文件,包含render函數。

function render(vnode,container){
    // vnode->node
    mount(vnode,container); // 待實現

}

const ReactDOM={
    render
}

export default ReactDOM;
複製代碼

這樣就實現了代碼中的常見的ReactDOM.render(jsx,container)。接下來重點實現mount函數來處理vnode掛載。
建立./simple-react/virtual-dom.js文件。

export function mount(vnode,container){
    const {vtype}=vnode;
    if(!vtype){
        // 純文本節點
        mountText(vnode,container);
    }
    if(vtype===1){
        // 原生節點
        mountHtml(vnode,container);
    }
    if(vtype===2){
        // 建立函數式節點
        mountFunc(vnode,container)
    }
    if(vtype===3){
        // 建立class類型組件
        mountClass(vnode,container);
    }
}

function mountText(vnode,container){
    let textNode=document.createTextNode(vnode);
    container.appendChild(textNode)
}

function mountHtml(vnode,container){
    const {type,props}=vnode;
    let htmlNode=document.createElement(type);
    const {children,...rest}=props;
    Object.keys(rest).map(item=> {
        if(item==="className"){
            htmlNode.setAttribute("class",rest[item]);
        }
        if(item.slice(0,2)==="on"){
            // 簡單處理click事件,實際狀況很複雜,須要考慮多種狀況
            htmlNode.addEventListener("click",rest[item])
        }
    })
    children.map(item=>{
        if(Array.isArray(item)){
            item.map(i=>mount(i,htmlNode))
        }else{
            mount(item,htmlNode)
        }
    })
    container.appendChild(htmlNode)
}

function mountFunc(vnode,container){
    const {type,props}=vnode;
    let node=type(props);
    mount(node,container);
}

function mountClass(vnode,container){
    const {type,props}=vnode;
    let cmp=new type(props);
    let node=cmp.render();
    mount(node,container);
}
複製代碼

mount函數建立好以後,完整的ReactDOM文件就能夠改寫爲以下:

import {mount} from "./virtual-dom";

function render(vnode,container){
    // vnode->node
    mount(vnode,container)

}

const ReactDOM={
    render
}

export default ReactDOM;
複製代碼

相關文件建立好以後,在由creat-react-app建立好的項目中改寫index.js文件,引入本身寫好的簡版react文件,進行測試。

import React from "./simple-react";
import ReactDOM from "./simple-react/ReactDOM";
import './index.css'
function Funcomp(props){
    return (
        <div className="border">{props.name}</div>
    )
}

class ClassComp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {  }
    }
    handleClick=()=>{
        alert("hello")
    }
    render() { 
        return (
            <div className="border">
                <h1>{this.props.name}</h1>
                {
                    [1,2,3].map(item=>
                    (
                        <h1 key={item}>{item}</h1>
                    )
                )
                }
                <button onClick={this.handleClick}>點我</button>
            </div>
        );
    }
}

let jsx=(
    <div>
        <div className="border">我是內容</div>
        <Funcomp name="我是函數組件內容" />
        <ClassComp name="我是class組件內容" />
    </div>
)
ReactDOM.render(jsx,document.getElementById("root"))
複製代碼

實際頁面效果若是和下面截圖同樣,表明着簡版react大功告成。

總結

  • webpack+babel編譯時,替換jsx爲React.createElement(type,props,...children)。
  • 全部React.createElement()執⾏結束後獲得⼀個JS對象即vdom,一個可以完整描述dom結構的對象。
  • ReactDOM.render(vdom,container)能夠將vdom轉換爲真實dom並添加container中。

固然在實際react處理中,要處理的狀況遠比上面寫的複雜多得多,dom的更新、替換、刪除要通過diff的過程,打補丁,更新布丁等過程,v16.8以後的fiber更是相似於時間分片的方式,將任務拆分,將高優先級任務優先執行,提升頁面渲染的流暢度等等,有不少須要咱們掌握的東西。

應了那句話:路漫漫其修遠兮,吾將上下而求索。

歡迎點贊、留言、交流。

參考資料

相關文章
相關標籤/搜索