实现简易的 React —— 渲染组件

分析源码有利于帮助理解数据流动,并且学习到高级的编程技巧。
初次使用 一个可运行案例
Peact
本文以 Peact 做为库的名称。
// 接口命名 const Peact = { /** * 创造 Peact element * @param {*} type (String) dom 类型 * @param {*} props (Object) Peact element 属性 * @param {*} children (Peact element or Dom or Basic type) 子节点 */ createElement(type, props, children){}, /** * 创造 Peact class * @param {*} sepc Peact class 声明 */ createClass( sepc ){}} const PeactDom = { /* 渲染函数 * @param {*} element Peact class 或 Peact element * @param {*} container 容器DOM节点 */ render(element, container){} }

实现 render 和 createElement
Peact 以实现两种组件类型,PeactElement 和 PeactClass 。
PeactElement 的定义:
// es6 class 实现 class PeactDOMComponent { constructor(element /* Peact element */){ this._currentElement = element } // 安装DOM节点 mountComponent(container){ // create HTML dom const domElement = document.createElement(this._currentElement.type); // 显式表现结果 const children = this._currentElement.props.children; const props = this._currentElement.props if( typeof children === "string" ){ const textNode = document.createTextNode(children); domElement.appendChild(textNode); } container.appendChild(domElement); return domElement; } } // 或其他实现 function PeactDOMComponent(element) { this._currentElement = element } PeactDOMComponent.prototype.mountComponent = function(container){ // create HTML dom const domElement = document.createElement(this._currentElement.type); // 显式表现结果 const children = this._currentElement.props.children; const props = this._currentElement.props if( typeof children === "string" ){ const textNode = document.createTextNode(children); domElement.appendChild(textNode); } container.appendChild(domElement); return domElement; }

ps.以后均已 class 实现
createElement 的简单实现:
/* create a Peact element */ function createElement (type, props, children){ const element = { type, props: props || {} }; if (children) { element.props.children = children; } return element; }

render 的简单实现:
function render(element /* Peact class or Peact element */, container){ const componentInstance = new PeactDOMComponent(element); return componentInstance.mountComponent(container); }

实践
// create a Peact element let MyDiv = Peact.createElement("div", null, "this is a div") Peact.render( MyDiv, document.body )

实现 createClass
createClass 的简单实现:
function createClass( sepc ){ // create a Peact class function ElementClassConstructor(props){ this.props = props // } // render 为必须函数 if( !sepc.render ){ console.error("Required render function!") return {}; } ElementClassConstructor.prototype.render = sepc.render return ElementClassConstructor }

此时如果用 createClass 创建 PeactClass 是不能直接用 Peact.render,应将PeactClass 转化成 PeactElement再利用 createClass 中配置 render 返回 PeactElement 实际进行绘制。
// create a Peact class & return Peact element const MyPeactClass = Peact.createClass({ render(){ this.props = { msg: "Hi!" } return Peact.createElement('h1', null, this.props.msg ); } }) Peact.render( MyPeactClass, document.body )

将PeactClass 转化成 PeactElement
class PeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } // 安装 component mountComponent(container) { const Component = this._currentElement; if(typeof Component === 'function') { // render 为 Peact class 声明时的 render element = (new Component()).render(); } // 确保此处的 element 为 Peact element const domComponentInstance = new PeactDOMComponent(element); domComponentInstance.mountComponent(container); } }

调整 render 统一接口
function render(element /* Peact class or Peact element */, container){ const componentInstance = new PeactCompositeComponentWrapper(element); return componentInstance.mountComponent(container); }

是否还能改进?
  • 对 render 函数的第一个参数 element 做了封装,并修改了 PeactCompositeComponentWrapper 使其支持子节点
利用一个高阶函数或 class
const ComponentWrapper = function(props) { this.props = props; }; ComponentWrapper.prototype.render = function() { return this.props; };

调整 render
render(element /* Peact class or Peact element */, container){ // wrapperElement { type: ComponentWrapper, props: element, children: undefined } const wrapperElement = this.createElement(ComponentWrapper, element); const componentInstance = new PeactCompositeComponentWrapper(wrapperElement); return componentInstance.mountComponent(container); }

调整 PeactCompositeComponentWrapper
class PeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; } mountComponent(container) { // this._currentElement { type: ComponentWrapper, props: element, children: undefined } // Component 就是 ComponentWrapper 构造函数 const Component = this._currentElement.type; // this._currentElement.props 就是 Peact.render() 第一个参数 element const componentInstance = new Component(this._currentElement.props); // 如果是 Peact element,则 element 是 Peact.render() 第一个参数,如果是 Peact class,element 就是 Peact.createClass 内部的构造函数 ElementClass let element = componentInstance.render(); // 对 element 类型判断决定,如果是 Peact class 则element 是构造函数,如果是 Peact element,element 是string, while (typeof element === 'function') { // render 为 Peact class 声明时的 render element = (new element(element.props)).render(); } // 确保此处的 element 为 Peact element const domComponentInstance = new PeactDOMComponent(element); domComponentInstance.mountComponent(container); } }

至此,一个简易的Peact渲染模型搭建完成,延伸部分还应包括对基础类型(number, string)和dom的支持
对基础类型(number, string)的支持
文本组件 PeactTextComponent
// Peact 文本组件 string or number function PeactTextComponent (text) { this._currentElement = '' + text; } PeactTextComponent.prototype.mountComponent = function(container){ const domElement = document.createElement("span"); domElement.innerHTML = this._currentElement container.appendChild(domElement); return domElement }

调整 render
function render(element /* Peact class or Peact element */, container){ // 类型判断 if( element.isPeactCreate && element.isPeactCreate() ){ // wrapperElement { type: ComponentWrapper, props: element, children: undefined } const wrapperElement = this.createElement(ComponentWrapper, element); const componentInstance = new PeactCompositeComponentWrapper(wrapperElement); return componentInstance.mountComponent(container); } // DOM or basic else if(typeof element === "string" || typeof element === "number"){ const componentInstance = new PeactTextComponent(element); return componentInstance.mountComponent(container); } }

isPeactCreate 是手动实现的Peact element 和 Peact class 判断,也可以忽略。
// 类型判断:是否为 Peact 元素 function isPeactCreate () { const t = this._t_ if( t === "PeactElement" || t === "PeactClass" ) { return true } return false; }

增加子节点遍历
调整 createElement
createElement(type, config, children) { /* create a Peact element */ function Element(type, key, props) { this.type = type this.key = key; this.props = props; }let props = extend({}, config)// 支持不定参数,并均合并至 children 中 var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = Array.isArray(children) ? children : [children]; } else if (childrenLength > 1) { var childArray = []; for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; }return new Element(type, null, props) }

调整 PeactDOMComponent
class PeactDOMComponent { constructor(element /* Peact element */ ) { this._currentElement = element }mountComponent(nodeID) { // create HTML dom this._nodeID = nodeIDconst children = this._currentElement.props.children; const props = this._currentElement.propsconst domElement = document.createElement(this._currentElement.type); domElement.setAttribute("data-peactid", nodeID)// 注册事件监听 if (props.onClick) { domElement.onclick = props.onClick }children.forEach((child, key) => { let childComponentInstance = PeactDOM.instantiatePeactComponent(child) let childID = nodeID + "." + key childComponentInstance._mountIndex = key; let childDomElement = childComponentInstance.mountComponent(childID) domElement.appendChild(childDomElement) })return domElement; } }

最后完整代码
const ComponentWrapper = function (props) { this.props = props; }; ComponentWrapper.prototype.render = function () { return this.props; }; function PeactDOMTextComponent(text) { this._currentElement = '' + text; }PeactDOMTextComponent.prototype.mountComponent = function () { const domElement = document.createElement("span"); domElement.innerHTML = this._currentElement return domElement }class PeactDOMComponent { constructor(element /* Peact element */ ) { this._currentElement = element }mountComponent(nodeID) { // create HTML dom this._nodeID = nodeIDconst children = this._currentElement.props.children; const props = this._currentElement.propsconst domElement = document.createElement(this._currentElement.type); domElement.setAttribute("data-peactid", nodeID)// 注册事件监听 if (props.onClick) { domElement.onclick = props.onClick }children.forEach((child, key) => { let childComponentInstance = PeactDOM.instantiatePeactComponent(child) let childID = nodeID + "." + key childComponentInstance._mountIndex = key; let childDomElement = childComponentInstance.mountComponent(childID) domElement.appendChild(childDomElement) })return domElement; } }class PeactCompositeComponentWrapper { constructor(element) { this._currentElement = element; }mountComponent(container) { const Component = this._currentElement.type; const componentInstance = new Component(this._currentElement.props); let element = componentInstance.render(); while (typeof element === 'function') { element = (new element(element.props)).render(); }const domComponentInstance = new PeactDOMComponent(element); domComponentInstance.mountComponent(container); } }// Peact 实现 const Peact = { createElement(type, config, children) { /* create a Peact element */ function Element(type, key, props) { this.type = type this.key = key; this.props = props; }let props = extend({}, config)var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = Array.isArray(children) ? children : [children]; } else if (childrenLength > 1) { var childArray = []; for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } props.children = childArray; }return new Element(type, null, props) },createClass(sepc) { // create a Peact class function ElementClassConstructor(props) { this.props = props }if (!sepc.render) { console.error("Required render function!") return {}; }if (Object.assign) { ElementClassConstructor.prototype = Object.assign(ElementClassConstructor.prototype, sepc) } // extend 手动支持 else { ElementClassConstructor.prototype = extend(ElementClassConstructor.prototype, sepc) }return ElementClassConstructor },}const PeactDOM = { instantiatePeactComponent(node) { // 文本节点的情况 if (typeof node === "string" || typeof node === "number") { return new PeactDOMTextComponent(node); } // 自定义的元素节点及原生DOM const wrapperNode = Peact.createElement(ComponentWrapper, node); return new PeactCompositeComponentWrapper(wrapperNode); }, render(element /* Peact class or Peact element */ , container) { const rootID = "peact" const componentInstance = PeactDOM.instantiatePeactComponent(element) const component =componentInstance.mountComponent(rootID); container.appendChild(component); } }

【实现简易的 React —— 渲染组件】实践
function notThis() { console.log(this) } // create a Peact class const MyPeactClass = Peact.createClass({ test() { console.log("this is test") console.log('this is THIS pointer: ', this) console.log('this is PROPS: ', this.props) console.log('this is STATE: ', this.state) }, say: notThis, render() { this.props = { msg: "Hi!" } // this.test() // this.say() return Peact.createElement('h1', { onClick: function () { alert("Hi!") } }, this.props.msg); } })PeactDOM.render( MyPeactClass, document.body )// create a Peact element let MyDiv = Peact.createElement("div", null, "this is a div") PeactDOM.render( MyDiv, document.body )// just render PeactDOM.render("this is text", document.body)// create a Peact element let MyH3 = Peact.createElement( "div", null, Peact.createElement("h3", null, "this is inner child!") ) PeactDOM.render( MyH3, document.body )

    推荐阅读