實現一個簡易版的react

實現一個簡易版的react


React.createElement

作用:將babel解析的結果轉換成樹形結構

class Element {
    constructor(type, props) {
        this.type = type;
        this.props = props;
    }
}
function createElement(type, props, ...children) {
    props = props || {};
    props.children = children;
    return new Element(type, props);
}
export default createElement

轉化前:

let element = React.createElement("h1",
  { class: "app", style: "color:red;font-size:100px;" },
  "hello",
  React.createElement("button", null, "123"));

轉化後:

React.render

作用:渲染元素到對應位置

/* react.js */
import $ from 'jquery'
import createElement from './element'
import createReactUnit from './unit';
import Component from './component'
/* React對象 */
let React = {
    createElement,
    render,
    nextRootIndex: 0,/* 元素編號 */
    Component
}
/* render負責將轉化的element渲染到頁面 */
function render(element, container) {
    /* 創建單元並編號 */
    let createReactUnitInstance = createReactUnit(element);
    let markUp = createReactUnitInstance.getMarkUp(React.nextRootIndex);
    /* 渲染到容器上 */
    $(container).html(markUp);
    /* 觸發訂閱函數 */
    $(document).trigger('mounted');
}

createReactUnit

createReactUnit函數負責將傳入的格式化element分爲三類分別處理(文本,原生元素,組件),另外創建一個父類,減少冗餘代碼

import $ from 'jquery'
/* 創建一個父類,放置多次重複寫constructor */
class Unit {
    constructor(element) { this.currentElement = element;  }
}
/* 文本單元 */
class ReactTextUnit extends Unit {
    getMarkUp(rootId) {
        //...
    }
}
/* 原生單元 */
class ReactNativeUnit extends Unit {
    getMarkUp(rootId) {
        //...
    }
}
/* 組件單元 */
class ReactComponent extends Unit {
    getMarkUp(rootId) {
        //...
    }
}
/* 根據不同類型生成不同單元 */
function createReactUnit(element) {
    /* 創建文本單元 */
    if (typeof element === "string" || typeof element === "number") {
        return new ReactTextUnit(element);
    }
    /* 創建標籤 */
    if (typeof element === "object" && typeof element.type === "string") {
        return new ReactNativeUnit(element);
    }
    /* 組件 */
    if (typeof element === "object"  && typeof element.type === "function") {
        return new ReactComponent(element);
    }
}
export default createReactUnit
  1. 文本
    直接在用span包圍並記錄data-reactid
class ReactTextUnit extends Unit {
    getMarkUp(rootId) {
        /* 存rootId */
        this._rootId = rootId;
        /* <span data-reactid="0">111</span> */
        return `<span data-reactid="${rootId}">${this.currentElement}</span>`;
    }
}
  1. 原生標籤
    通過字符串拼接的方式連接屬性,對於children,通過遞歸的方式創建子單元,用一個字符串content來存生成的標籤字符串,對於onClick等事件綁定,使用$(document).on()綁定事件解決字符串無法綁定的問題
class ReactNativeUnit extends Unit {
    getMarkUp(rootId) {
        this._rootId = rootId;
        /* 提取數據 */
        let { type, props } = this.currentElement;
        /* 創建外圍標籤 */
        let tagStart = `<${type} data-reactid=${rootId}`, tagEnd = `</${type}>`;
        /* 遍歷標籤屬性 */
        let content = "";
        for (let key in props) {
            /* 兒子結點 */
            if (key === "children") {
                content += props[key].map((child, index) => {
                    let childUnit = createReactUnit(child);
                    return childUnit.getMarkUp(`${rootId}.${index}`)
                }).join('');
            }
            /* {onClick:show} 事件 */
            else if (/^on[A-Z]/.test(key)) {
                /* 綁定事件 */
                let eventType = key.slice(2).toLowerCase();
                $(document).on(eventType, `[data-reactid="${rootId}"]`, props[key]);
            }
            /* 普通屬性 */
            else
                tagStart += ` ${key}="${props[key]}" `;
        }
        /* 拼接返回 */
        return `${tagStart}>${content}${tagEnd}`;
    }
}

傳入element

(
<h1 class="app" style="color:red;font-size:100px;">
    hello
    <button onClick={show}>123</button>
</h1>
)

效果

  1. 組件
    babel解析組件的結果第一個值是Counter類,調用createElement後賦值到type上,生成單元時可以通過type獲取到Counter,然後新建實例獲得類中render方法的結果,對結果進行創建單元和調用getMarkUp方法,得到標籤字符串markUp並返回,另外還可以通過創建實例的方式獲取生命週期函數,組件掛載前直接調用,頁面掛載後通過$(document).trigger('mounted')觸發執行
組件jsx格式:
<Counter name="mike"/>
解析結果:
React.createElement(Counter, {
  name: "mike"
});
class ReactComponent extends Unit {
    getMarkUp(rootId) {
        this._rootId = rootId;
        /* 獲取到Conponent類 */
        let { type: Component, props } = this.currentElement;
        let componentInstance = new Component(props);
        /* 掛載前生命週期函數 */
        componentInstance.componentWillMount &&
            componentInstance.componentWillMount();
        // 獲取實例render返回值
        let element = componentInstance.render();
        let markUp = createReactUnit(element).getMarkUp(rootId);
        /* 掛載後生命週期 */
        $(document).on('mounted', () => {
            componentInstance.componentDidMount &&
                componentInstance.componentDidMount();
        });
        return markUp;
    }
}

示例:

class SubCounter {
  componentWillMount() {
    console.log("child  即將掛載");
  }
  componentDidMount() {
    console.log("child  掛載完成");
  }
  render() {
    return <h1 style="color:green" onClick={show}>888</h1>
  }
}
class Counter extends React.Component {
  constructor(props) {
    super(props);
    this.state = { number: 1 }
  }
  componentWillMount() {
    console.log("parent  即將掛載");
  }
  componentDidMount() {
    console.log("parent  掛載完成")
  }
  render() {
    return <SubCounter />;
  }
}
React.render(
  <Counter name="mike" />,
  document.getElementById('root')
);

效果:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章