乐趣区

关于react.js:实现简易的-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 = nodeID

    const children = this._currentElement.props.children;
    const props = this._currentElement.props

    const 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 = nodeID

    const children = this._currentElement.props.children;
    const props = this._currentElement.props

    const 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);
  }
}

实际

    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
    )
退出移动版