实现一个简易版的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
- 文本
直接在用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')
);
效果: