分析源码有利于帮助理解数据流动,并且学习到高级的编程技巧。

初次使用

一个可运行案例

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
    )

donggg
203 声望584 粉丝

> github


下一篇 »
React Conf 2021