剖析源码有利于帮忙了解数据流动,并且学习到高级的编程技巧。
首次应用
一个可运行案例
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
)