共计 10514 个字符,预计需要花费 27 分钟才能阅读完成。
剖析源码有利于帮忙了解数据流动,并且学习到高级的编程技巧。
首次应用
一个可运行案例
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 | |
) |
正文完