實現一個簡易版的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
- 文本
直接在用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>`;
}
}
- 原生標籤
通過字符串拼接的方式連接屬性,對於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>
)
效果
- 組件
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')
);
效果: