翻譯自: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內容。
相關內容到此結束。