分析源码有利于帮助理解数据流动,并且学习到高级的编程技巧。初次使用 一个可运行案例
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 使其支持子节点
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
)