翻译自:https://engineering.hexacta.c…
上一节的代码有一些题目:
- 每次更新都邑带来整颗假造DOM树的一致性校验;
- 状况是全局的(没有私有状况);
- 有变化发生后必需手动挪用
render
要领以便将变化反应到页面上。
组件能够帮我们处置惩罚上面的题目,同时还能带来一些新特征:
- 许可自定义JSX的标署名
- 生命周期钩子(这一节暂不引见这部份)
起首我们要定义一个Component
的基本类,在建立别的组件时都要继续该类。我们须要一个带有props
入参和setState
要领的组织函数,setState
要领能够吸收partialState
作为入参来更新组件状况:
class Component{
constructor(props){
this.props = props;
this.state = this.state || {}
}
setState(partialState){
this.state = Object.assign({}, this.state, partialState);
}
}
我们在建立组件时都邑继续上面这个类。组件的运用要领和原生的标签如div
或许span
一样,直接像如许<MyComponent />
就能够了。而且我们的createElement
也不须要做修正,元素的type
属性能够直接取值为组件类,剩下的props
属性也不须要迥殊的处置惩罚。我们须要一个要领能依据传入的元夙来建立组件的实例(称之为大众实例,实在就是依据这个组织函数new出来的一个对象)。
function createPublicInstance(element, internalInstance){
const {type, props} = element;
const publicInstance = new type(props); // 这处所的type对应组件的组织函数
publicInstance.__internalInstance = internalInstance;
return publicInstance;
}
组件的内部实例含有组件对应的dom元素(内部实例就是前几节我们说的实例,经由过程挪用instantiate
要领天生的)。大众实例与内部实例的援用关联会被保留着,经由过程这个援用关联能够找到大众实例对应的内部实例及假造DOM,当大众实例状况发生变化时,我们就能够只更新发生变化的内部实例及其对应的那部份假造DOM:
class Component{
constructor(props){
this.props = props;
this.state = this.state || {}
}
setState(partialState){
this.state = Object.assign({}, this.state, partialState);
updateInstance(this.__internalInstance);
}
}
function updateInstance(internalInstance){
const parentDom = internalInstance.dom.parentNode;
const element = internalInstance.element;
reconcile(parentDom, internalInstance, element);
}
instantiate
要领须要做一些革新。对组件来说,我们须要先建立大众实例(先new一个组建),然后挪用组件的render
要领来猎取组件内部的元素,末了把猎取到的元素传递给instantiate
要领。
function instantiate(element){
const { type, props } = element;
const isDomElement = typeof type === 'string';
if(isDomElement){ // 如果是原生的dom元素的话,直接建立实例
const isTextElement = type === TEXT_ELEMENT;
const dom = isTextElement
? document.createTextNode('')
: document.createElement(type);
updateDomProperties(dom, [], props);
const childElements = props.children || [];
const childInstances = childElements.map(instantiate);
const childDoms = childInstances.map(childInstance => childInstance.dom);
childDoms.forEach(childDom => dom.appendChild(childDom));
const instance = { dom, element, childInstances };
return instance;
} else {// 不然先建立大众实例,然后再挪用instantiate要领建立内部实例
const instance = {};
// 这处所的element是一个type属性为一个组织函数的对象
const publicInstance = createPublicInstance(element, instance);
const childElement = publicInstance.render();
const childInstance = instantiate(childElement);
const dom = childInstance.dom;
Object.assign(instance, { dom, element, childInstance, publicInstance});
return instance;
}
}
组件对应的内部实例和原生dom元素对应的实例有些不一样。组件内部实例只会具有一个子元素,即render
要领返回的内容,而原生dom元素则能够含有多个子元素。所以关于组件内部实例来说,它们会有一个childInstance
属性而不是一个childInstances
数组。另外,由于在举行一致性校验时须要挪用组件的render
要领,所以组件内部实例会保留对大众实例的援用(反过来大众实例也保留着对内部实例的援用)。
接下来我们来处置惩罚下组件实例的一致性校验。由于组件的内部实例只含有一个子元素(一切元素有一个一致的父类),只须要更新大众实例的props
属性,实行render
要领猎取子元素,然后再举行一致性校验就能够了。
function reconcile(parentDom, instance, element){
if(instance == null){
const newInstance = instantiate(element);
parentDom.appendChild(newInstance.dom);
return newInstance;
} else if( element == null){
parentDom.removeChild(instance.dom);
return null;
} else if(instance.element.type !== element.type){
const newInstance = instantiate(element);
parentDom.replaceChild(newInstance.dom, instance.dom);
return newInstance;
} else if(typeof element.type === 'string'){
updateDomProperties(instance.dom, instance.element, props, element.props);
instance.childInstances = reconcileChildren(instance, element);
instance.element = element;
return instance;
} else {
instance.publicInstance.props = element.props;// 更新大众实例的props
const childElement = instance.publicInstance.render(); // 猎取最新的子元素
const oldChildInstance = instance.childInstance;
const childInstance = reconcile(parentDom, oldChildInstance, childElement);
instance.dom = childInstance.dom;
instance.childInstance = childInstance;
instance.element = element;
return instance;
}
}
如今,我们的Didact.js已能够支撑组件了。这里能够在线编辑代码并能看到结果。
运用组件后,我们能够建立自定义的JSX标签,并具有了组件内部状况,而且组件有变化时只会变动本身的那部份dom内容。
相关内容到此结束。