摘要:現(xiàn)在流行的前端框架都支持自定義組件,組件化開發(fā)已經(jīng)成為提高前端開發(fā)效率的銀彈。二對自定義組件的支持要想正確的渲染組件,第一步就是要告訴某個標簽是自定義組件。下面的例子里,就是一個自定義組件。解決了識別自定義標簽的問題,下一步就是定義標簽了。
歡迎關注我的公眾號睿Talk,獲取我最新的文章:
目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染效率。那么,什么是Virtual DOM?它是通過什么方式去提升頁面渲染效率的呢?本系列文章會詳細講解Virtual DOM的創(chuàng)建過程,并實現(xiàn)一個簡單的Diff算法來更新頁面。本文的內(nèi)容脫離于任何的前端框架,只講最純粹的Virtual DOM。敲單詞太累了,下文Virtual DOM一律用VD表示。
這是VD系列文章的第五篇,以下是本系列其它文章的傳送門:
你不知道的Virtual DOM(一):Virtual Dom介紹
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新優(yōu)化
你不知道的Virtual DOM(四):key的作用
你不知道的Virtual DOM(五):自定義組件
你不知道的Virtual DOM(六):事件處理&異步更新
今天,我們繼續(xù)在之前項目的基礎上擴展功能?,F(xiàn)在流行的前端框架都支持自定義組件,組件化開發(fā)已經(jīng)成為提高前端開發(fā)效率的銀彈。下面我們就將自定義組件功能加到項目中去,目標是正確的渲染和更新自定義組件。
二、JSX對自定義組件的支持要想正確的渲染組件,第一步就是要告訴JSX某個標簽是自定義組件。這個實現(xiàn)起來很簡單,只要標簽名的首字母大寫就可以了。下面的例子里,MyComp就是一個自定義組件。
普通標簽
經(jīng)過JSX編譯后,是下面這個樣子。
h( "div", null, h( "div", null, "u666Eu901Au6807u7B7E" ), h(MyComp, null) );
當首字母大寫當時候,JSX會將標簽名當作變量處理,而不是像普通標簽一樣當字符串處理。解決了識別自定義標簽的問題,下一步就是定義標簽了。
三、定義基類Component在React中,所有自定義組件都要繼承Component基類,它為我們提供了一系列生命周期方法和修改組件的方法。我們也對應的定義一個自己的Component類:
class Component { constructor(props) { this.props = props; this.state = {}; } setState(newState) { this.state = {...this.state, ...newState}; const vdom = this.render(); diff(this.dom, vdom, this.parent); } render() { throw new Error("component should define its own render method") } };
如果用一句話描述Component,那就是屬性和狀態(tài)的UI表達。我們先不考慮生命周期函數(shù),先定義一個最精簡版的Component。首先在初始化的時候,需要傳入props屬性,然后提供一個setState方法來改變組件的狀態(tài),最后就是子類必須要實現(xiàn)的render函數(shù)。如果子類沒有實現(xiàn),就會沿著原型鏈查找到Component類,然后會拋出一個錯誤。
有了Component基類后,我們就可以定義自己的組件了。我們來定義一個最簡單的顯示屬性和狀態(tài)信息的組件。
class MyComp extends Component { constructor(props) { super(props); this.state = { name: "Tina" } } render() { return() } }This is My Component! {this.props.count}name: {this.state.name}
定義好組件后,就要考慮渲染的邏輯了。
四、組件渲染邏輯在對VD進行diff操作的時候,要對tag為函數(shù)類型(自定義組件)的節(jié)點做特殊處理,同時對新建的節(jié)點,也要加入一些額外的邏輯。
function diff(dom, newVDom, parent, componentInst) { if (typeof newVDom == "object" && typeof newVDom.tag == "function") { buildComponentFromVDom(dom, newVDom, parent); return false; } // 新建node if (dom == undefined) { const dom = createElement(newVDom); // 自定義組件 if (componentInst) { dom._component = componentInst; dom._componentConstructor = componentInst.constructor; componentInst.dom = dom; } parent.appendChild(dom); return false; } ... } function buildComponentFromVDom(dom, vdom, parent) { const cpnt = vdom.tag; if (!typeof cpnt === "function") { throw new Error("vdom is not a component type"); } const props = getVDomProps(vdom); let componentInst = dom && dom._component; // 創(chuàng)建組件 if (componentInst == undefined) { try { componentInst = new cpnt(props); setTimeout(() => {componentInst.setState({name: "Dickens"})}, 5000); } catch (error) { throw new Error(`component creation error: ${cpnt.name}`); } } // 組件更新 else { componentInst.props = props; } const componentVDom = componentInst.render(); diff(dom, componentVDom, parent, componentInst); } function getVDomProps(vdom) { const props = vdom.props; props.children = vdom.children; return props; }
如果是自定義組件,會調(diào)用buildComponentFromVDom方法。先通過getVDomProps方法獲取vdom最新的屬性,包括children。如果dom對象有_component屬性,說明是組件更新的過程,否則為組件創(chuàng)建的過程。如果是創(chuàng)建過程則直接實例化一個對象,setTimeout部分主要為了驗證setState能不能正常工作,可以先忽略。如果是更新過程,則傳入最新的props。最后通過組件的render方法得到最新的vdom后,再進行diff操作。
diff多了一個componentInst的參數(shù),在新建dom節(jié)點的時候,如果有這個參數(shù),說明是自定義組件創(chuàng)建的節(jié)點,需要用_component和_componentConstructor做一下標識。其中_component上面就用到了,用來判斷是組件更新過程還是組件創(chuàng)建過程。_componentConstructor用在組件更新過程中判斷組件的類型是否相同。
function isSameType(element, newVDom) { if (typeof newVDom.tag == "function") { return element._componentConstructor == newVDom.tag; } ... }
到此為止,自定義組件的被動更新過程已經(jīng)完成了,下面來看看主動更新的邏輯。
五、setStatesetState的邏輯很簡單,就是更新state后再render一次,獲取到最新的vdom,再走一遍diff的過程。setState的前提是組件已經(jīng)實例化并且已經(jīng)渲染出來了,this.dom就是組件渲染出來的dom的頂級節(jié)點。
setState(newState) { this.state = {...this.state, ...newState}; const vdom = this.render(); diff(this.dom, vdom, this.parent); } function buildComponentFromVDom(dom, vdom, parent) { ... // 創(chuàng)建組件 if (componentInst == undefined) { ... setTimeout(() => {componentInst.setState({name: "Dickens"})}, 5000); ... }
為了驗證setState能否按預期運行,在創(chuàng)建組件的時候我們在5秒后更新一下state,看看名字能否正確更新。我們的頁面是長這個樣子的:
function view() { const elm = arr.pop(); // 用于測試能不能正常刪除元素 if (state.num !== 9) arr.unshift(elm); // 用于測試能不能正常添加元素 if (state.num === 12) arr.push(9); return (Hello World); }{ arr.map( i => (
- 第{i}
)) }
剛開始渲染出來是這個樣子:
5秒之后是這個樣子:
可以看到props和state都得到了正確都渲染。
六、總結本文基于上一個版本的代碼,加入了對自定義組件的支持,大大提高代碼的復用性?;诋斍斑@個版本的代碼還能做怎樣的優(yōu)化呢,請看下一篇的內(nèi)容:你不知道的Virtual DOM(六):事件處理&異步更新。
P.S.: 想看完整代碼見這里,如果有必要建一個倉庫的話請留言給我:代碼
文章版權歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/97307.html
摘要:如果列表是空的,則存入組件后將異步刷新任務加入到事件循環(huán)當中。四總結本文基于上一個版本的代碼,加入了事件處理功能,同時通過異步刷新的方法提高了渲染效率。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DO...
摘要:最后里面沒有第四個元素了,才會把蘋果從移除。四總結本文基于上一個版本的代碼,加入了對唯一標識的支持,很好的提高了更新數(shù)組元素的效率。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染...
摘要:不同的框架對這三個屬性的命名會有點差別,但表達的意思是一致的。它們分別是標簽名屬性和子元素對象。我們先來看下頁面的更新一般會經(jīng)過幾個階段。元素有可能是數(shù)組的形式,需要將數(shù)組解構一層。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約...
摘要:經(jīng)過這次優(yōu)化,計算的時間快了那么幾毫秒?;诋斍斑@個版本的代碼還能做怎樣的優(yōu)化呢,請看下一篇的內(nèi)容你不知道的四的作用。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染效率。那么,什...
摘要:變化的只有種更新和刪除。頁面的元素的數(shù)量隨著而變。四總結本文詳細介紹如何實現(xiàn)一個簡單的算法,再根據(jù)計算出的差異去更新真實的。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React 和 Vue,都不約而同的借助 Virtual DOM 技術提高頁面的渲染...
閱讀 1380·2021-11-15 18:14
閱讀 3359·2021-08-25 09:38
閱讀 2769·2019-08-30 10:55
閱讀 2841·2019-08-29 16:39
閱讀 1400·2019-08-29 15:07
閱讀 2536·2019-08-29 14:14
閱讀 907·2019-08-29 12:36
閱讀 998·2019-08-29 11:21