摘要:如果列表是空的,則存入組件后將異步刷新任務(wù)加入到事件循環(huán)當(dāng)中。四總結(jié)本文基于上一個(gè)版本的代碼,加入了事件處理功能,同時(shí)通過異步刷新的方法提高了渲染效率。
歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:
目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術(shù)提高頁面的渲染效率。那么,什么是Virtual DOM?它是通過什么方式去提升頁面渲染效率的呢?本系列文章會(huì)詳細(xì)講解Virtual DOM的創(chuàng)建過程,并實(shí)現(xiàn)一個(gè)簡單的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ù)在之前項(xiàng)目的基礎(chǔ)上擴(kuò)展功能。在上一篇文章中,介紹了自定義組件的渲染和更新的實(shí)現(xiàn)方法。為了驗(yàn)證setState是否生效,還定義了一個(gè)setTimeout方法,5秒后更新state。在現(xiàn)實(shí)的項(xiàng)目中,state的改變往往是通過事件觸發(fā)的,如點(diǎn)擊事件、鍵盤事件和滾動(dòng)事件等。下面,我們就將事件處理加入到項(xiàng)目當(dāng)中。
二、實(shí)現(xiàn)事件處理事件的綁定一般是定義在元素或者組件的屬性當(dāng)中,之前對(duì)屬性的初始化和更新沒有考慮支持事件,只是簡單的賦值操作。
// 屬性賦值 function setProps(element, props) { // 屬性賦值 element[ATTR_KEY] = props; for (let key in props) { element.setAttribute(key, props[key]); } } // 比較props的變化 function diffProps(newVDom, element) { let newProps = {...element[ATTR_KEY]}; const allProps = {...newProps, ...newVDom.props}; // 獲取新舊所有屬性名后,再逐一判斷新舊屬性值 Object.keys(allProps).forEach((key) => { const oldValue = newProps[key]; const newValue = newVDom.props[key]; // 刪除屬性 if (newValue == undefined) { element.removeAttribute(key); delete newProps[key]; } // 更新屬性 else if (oldValue == undefined || oldValue !== newValue) { element.setAttribute(key, newValue); newProps[key] = newValue; } } ) // 屬性重新賦值 element[ATTR_KEY] = newProps; }
setProps是在創(chuàng)建元素的時(shí)候調(diào)用的,而diffProps則是在diff過程中調(diào)用的。如果需要支持事件綁定,我們需要多做一個(gè)判斷。如果屬性名稱是on開頭的話,比如onClick,我們就要在當(dāng)前元素上注冊(cè)或刪除一個(gè)事件處理。
// 屬性賦值 function setProps(element, props) { // 屬性賦值 element[ATTR_KEY] = props; for (let key in props) { // on開頭的屬性當(dāng)作事件處理 if (key.substring(0, 2) == "on") { const evtName = key.substring(2).toLowerCase(); element.addEventListener(evtName, evtProxy); (element._evtListeners || (element._evtListeners = {}))[evtName] = props[key]; } else { element.setAttribute(key, props[key]); } } } function evtProxy(evt) { this._evtListeners[evt.type](evt); } // 比較props的變化 function diffProps(newVDom, element) { let newProps = {...element[ATTR_KEY]}; const allProps = {...newProps, ...newVDom.props}; // 獲取新舊所有屬性名后,再逐一判斷新舊屬性值 Object.keys(allProps).forEach((key) => { const oldValue = newProps[key]; const newValue = newVDom.props[key]; // on開頭的屬性當(dāng)作事件處理 if (key.substring(0, 2) == "on") { const evtName = key.substring(2).toLowerCase(); if (newValue) { element.addEventListener(evtName, evtProxy); } else { element.removeEventListener(evtName, evtProxy); } (element._evtListeners || (element._evtListeners = {}))[evtName] = newValue; } else { // 刪除屬性 if (newValue == undefined) { element.removeAttribute(key); delete newProps[key]; } // 更新屬性 else if (oldValue == undefined || oldValue !== newValue) { element.setAttribute(key, newValue); newProps[key] = newValue; } } } ) // 屬性重新賦值 element[ATTR_KEY] = newProps; }
所有的事件處理函數(shù)都存到dom元素的_evtListeners當(dāng)中,當(dāng)事件觸發(fā)的時(shí)候,將事件傳給里面對(duì)應(yīng)的方法處理。這樣做的好處是如果以后要對(duì)瀏覽器傳入的事件evt做進(jìn)一步的封裝,就可以在evtProxy函數(shù)里面處理。
接下來,我們?cè)谧远x組件里面新增一個(gè)onClick事件,在點(diǎn)擊的時(shí)候改變state里面的值。
class MyComp extends Component { constructor(props) { super(props); this.state = { name: "Tina", count: 1 } } elmClick() { this.setState({name: `Jack${this.state.count}`, count: this.state.count + 1 }); } render() { return() } }This is My Component! {this.props.count}name: {this.state.name}
項(xiàng)目運(yùn)行的效果是每當(dāng)我點(diǎn)一下MyComp組件的區(qū)域,里面的name就會(huì)隨之馬上更新。
三、setState異步更新用過React的朋友都知道,為了減少不必要的渲染,提高性能,React并不是在我們每次setState的時(shí)候都進(jìn)行渲染,而是將一個(gè)同步操作里面的多個(gè)setState進(jìn)行合并后再渲染,給人異步渲染的感覺??催^源碼的都應(yīng)該知道,React是通過事務(wù)的方式來合并多個(gè)setState操作的,本質(zhì)來說還是同步的。如果想對(duì)其作更深入的學(xué)習(xí),推薦看這篇文章。
為了達(dá)到合并操作,減少渲染的效果,最簡單的方式就是異步渲染,下面我們來看看如何實(shí)現(xiàn)。在上一個(gè)版本里,setState是這么定義的:
class Component { ... setState(newState) { this.state = {...this.state, ...newState}; const vdom = this.render(); diff(this.dom, vdom, this.parent); } ... };
state更新后直接就進(jìn)行diff操作,進(jìn)而更新頁面。如果我們onClick里面的代碼改成這樣:
elmClick() { this.setState({name: `Jack${this.state.count}`, count: this.state.count + 1 }); this.setState({name: `Jack${this.state.count}`, count: this.state.count + 1 }); }
頁面會(huì)渲染2次。如果我們把它改造成下面的樣子:
// 等待渲染的組件數(shù)組 let pendingRenderComponents = []; class Component { ... setState(newState) { this.state = {...this.state, ...newState}; enqueueRender(this); } ... }; function enqueueRender(component) { // 如果push后數(shù)組長度為1,則將異步刷新任務(wù)加入到事件循環(huán)當(dāng)中 if (pendingRenderComponents.push(component) == 1) { if (typeof Promise=="function") { Promise.resolve().then(renderComponent); } else { setTimeout(renderComponent, 0); } } } function renderComponent() { // 組件去重 const uniquePendingRenderComponents = [...new Set(pendingRenderComponents)]; // 渲染組件 uniquePendingRenderComponents.forEach(component => { const vdom = component.render(); diff(component.dom, vdom, component.parent); }); // 清空待渲染列表 pendingRenderComponents = []; }
當(dāng)?shù)谝淮?b>setState成功后,并不會(huì)馬上進(jìn)行渲染,而是將組件存入待渲染組件列表當(dāng)中。如果列表是空的,則存入組件后將異步刷新任務(wù)加入到事件循環(huán)當(dāng)中。當(dāng)運(yùn)行環(huán)境支持Promise時(shí),通過微任務(wù)運(yùn)行,否則通過宏任務(wù)運(yùn)行。微任務(wù)的運(yùn)行時(shí)間是當(dāng)前事件循環(huán)的末尾,而宏任務(wù)的運(yùn)行時(shí)間是下一個(gè)事件循環(huán)。所以優(yōu)先使用微任務(wù)。
緊接著進(jìn)行第二次setState操作,同樣的,將組件存入待渲染組件列表當(dāng)中。此時(shí),主線程的任務(wù)執(zhí)行完了,開始執(zhí)行異步任務(wù)。
當(dāng)異步刷新任務(wù)啟動(dòng)時(shí),將待渲染列表去重后對(duì)里面的組件進(jìn)行渲染。等渲染完成后再清空待渲染列表。此時(shí),渲染出來的是2次setState合并后的結(jié)果,并且只會(huì)進(jìn)行一次diff操作,渲染一次。
四、總結(jié)本文基于上一個(gè)版本的代碼,加入了事件處理功能,同時(shí)通過異步刷新的方法提高了渲染效率。
這是VD系列的最后一篇文章。本系列從什么是Virtual Dom這個(gè)問題出發(fā),講解了VD的數(shù)據(jù)結(jié)構(gòu)、比較方式和更新流程,并在此基礎(chǔ)上進(jìn)行功能擴(kuò)展和性能優(yōu)化,支持key元素復(fù)用、自定義組件,dom事件綁定和setState異步更新。總共三百多行代碼,實(shí)現(xiàn)了mvvm庫的核心功能。
有關(guān)VD,如果還有什么想了解的,歡迎留言,有問必答。
P.S.: 想看完整代碼見這里,如果有必要建一個(gè)倉庫的話請(qǐng)留言給我:代碼
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/97573.html
摘要:現(xiàn)在流行的前端框架都支持自定義組件,組件化開發(fā)已經(jīng)成為提高前端開發(fā)效率的銀彈。二對(duì)自定義組件的支持要想正確的渲染組件,第一步就是要告訴某個(gè)標(biāo)簽是自定義組件。下面的例子里,就是一個(gè)自定義組件。解決了識(shí)別自定義標(biāo)簽的問題,下一步就是定義標(biāo)簽了。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、...
摘要:最后里面沒有第四個(gè)元素了,才會(huì)把蘋果從移除。四總結(jié)本文基于上一個(gè)版本的代碼,加入了對(duì)唯一標(biāo)識(shí)的支持,很好的提高了更新數(shù)組元素的效率。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術(shù)提高頁面的渲染...
摘要:不同的框架對(duì)這三個(gè)屬性的命名會(huì)有點(diǎn)差別,但表達(dá)的意思是一致的。它們分別是標(biāo)簽名屬性和子元素對(duì)象。我們先來看下頁面的更新一般會(huì)經(jīng)過幾個(gè)階段。元素有可能是數(shù)組的形式,需要將數(shù)組解構(gòu)一層。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約...
摘要:變化的只有種更新和刪除。頁面的元素的數(shù)量隨著而變。四總結(jié)本文詳細(xì)介紹如何實(shí)現(xiàn)一個(gè)簡單的算法,再根據(jù)計(jì)算出的差異去更新真實(shí)的。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React 和 Vue,都不約而同的借助 Virtual DOM 技術(shù)提高頁面的渲染...
摘要:經(jīng)過這次優(yōu)化,計(jì)算的時(shí)間快了那么幾毫秒?;诋?dāng)前這個(gè)版本的代碼還能做怎樣的優(yōu)化呢,請(qǐng)看下一篇的內(nèi)容你不知道的四的作用。 歡迎關(guān)注我的公眾號(hào)睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術(shù)提高頁面的渲染效率。那么,什...
閱讀 2993·2021-10-13 09:39
閱讀 2694·2021-09-27 13:34
閱讀 2031·2019-08-30 15:55
閱讀 3260·2019-08-30 15:43
閱讀 3631·2019-08-30 11:16
閱讀 1748·2019-08-26 18:28
閱讀 1283·2019-08-26 13:56
閱讀 914·2019-08-26 13:35