翻译自:https://engineering.hexacta.c...javascript
上一节的代码有一些问题:java
render
方法以便将变化反应到页面上。组件能够帮咱们解决上面的问题,同时还能带来一些新特性:数组
首先咱们要定义一个Component
的基础类,在建立其它组件时都要继承该类。咱们须要一个带有props
入参和setState
方法的构造函数,setState
方法能够接收partialState
做为入参来更新组件状态:app
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出来的一个对象)。dom
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
方法。this
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
方法,因此组件内部实例会保存对公共实例的引用(反过来公共实例也保存着对内部实例的引用)。spa
接下来咱们来处理下组件实例的一致性校验。由于组件的内部实例只含有一个子元素(全部元素有一个统一的父类),只须要更新公共实例的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已经能够支持组件了。这里能够在线编辑代码并能看到效果。code
使用组件后,咱们能够建立自定义的JSX标签,并拥有了组件内部状态,并且组件有变化时只会变动本身的那部分dom内容。
相关内容到此结束。