摘要:如果列表是空的,則存入組件后將異步刷新任務加入到事件循環當中。四總結本文基于上一個版本的代碼,加入了事件處理功能,同時通過異步刷新的方法提高了渲染效率。
歡迎關注我的公眾號睿Talk,獲取我最新的文章:
目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染效率。那么,什么是Virtual DOM?它是通過什么方式去提升頁面渲染效率的呢?本系列文章會詳細講解Virtual DOM的創建過程,并實現一個簡單的Diff算法來更新頁面。本文的內容脫離于任何的前端框架,只講最純粹的Virtual DOM。敲單詞太累了,下文Virtual DOM一律用VD表示。
這是VD系列文章的第六篇,以下是本系列其它文章的傳送門:
你不知道的Virtual DOM(一):Virtual Dom介紹
你不知道的Virtual DOM(二):Virtual Dom的更新
你不知道的Virtual DOM(三):Virtual Dom更新優化
你不知道的Virtual DOM(四):key的作用
你不知道的Virtual DOM(五):自定義組件
你不知道的Virtual DOM(六):事件處理&異步更新
今天,我們繼續在之前項目的基礎上擴展功能。在上一篇文章中,介紹了自定義組件的渲染和更新的實現方法。為了驗證setState是否生效,還定義了一個setTimeout方法,5秒后更新state。在現實的項目中,state的改變往往是通過事件觸發的,如點擊事件、鍵盤事件和滾動事件等。下面,我們就將事件處理加入到項目當中。
二、實現事件處理事件的綁定一般是定義在元素或者組件的屬性當中,之前對屬性的初始化和更新沒有考慮支持事件,只是簡單的賦值操作。
</>復制代碼
// 屬性賦值
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是在創建元素的時候調用的,而diffProps則是在diff過程中調用的。如果需要支持事件綁定,我們需要多做一個判斷。如果屬性名稱是on開頭的話,比如onClick,我們就要在當前元素上注冊或刪除一個事件處理。
</>復制代碼
// 屬性賦值
function setProps(element, props) {
// 屬性賦值
element[ATTR_KEY] = props;
for (let key in props) {
// on開頭的屬性當作事件處理
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開頭的屬性當作事件處理
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;
}
所有的事件處理函數都存到dom元素的_evtListeners當中,當事件觸發的時候,將事件傳給里面對應的方法處理。這樣做的好處是如果以后要對瀏覽器傳入的事件evt做進一步的封裝,就可以在evtProxy函數里面處理。
接下來,我們在自定義組件里面新增一個onClick事件,在點擊的時候改變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}
)
}
}
項目運行的效果是每當我點一下MyComp組件的區域,里面的name就會隨之馬上更新。
三、setState異步更新用過React的朋友都知道,為了減少不必要的渲染,提高性能,React并不是在我們每次setState的時候都進行渲染,而是將一個同步操作里面的多個setState進行合并后再渲染,給人異步渲染的感覺。看過源碼的都應該知道,React是通過事務的方式來合并多個setState操作的,本質來說還是同步的。如果想對其作更深入的學習,推薦看這篇文章。
為了達到合并操作,減少渲染的效果,最簡單的方式就是異步渲染,下面我們來看看如何實現。在上一個版本里,setState是這么定義的:
</>復制代碼
class Component {
...
setState(newState) {
this.state = {...this.state, ...newState};
const vdom = this.render();
diff(this.dom, vdom, this.parent);
}
...
};
state更新后直接就進行diff操作,進而更新頁面。如果我們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 });
}
頁面會渲染2次。如果我們把它改造成下面的樣子:
</>復制代碼
// 等待渲染的組件數組
let pendingRenderComponents = [];
class Component {
...
setState(newState) {
this.state = {...this.state, ...newState};
enqueueRender(this);
}
...
};
function enqueueRender(component) {
// 如果push后數組長度為1,則將異步刷新任務加入到事件循環當中
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 = [];
}
當第一次setState成功后,并不會馬上進行渲染,而是將組件存入待渲染組件列表當中。如果列表是空的,則存入組件后將異步刷新任務加入到事件循環當中。當運行環境支持Promise時,通過微任務運行,否則通過宏任務運行。微任務的運行時間是當前事件循環的末尾,而宏任務的運行時間是下一個事件循環。所以優先使用微任務。
緊接著進行第二次setState操作,同樣的,將組件存入待渲染組件列表當中。此時,主線程的任務執行完了,開始執行異步任務。
當異步刷新任務啟動時,將待渲染列表去重后對里面的組件進行渲染。等渲染完成后再清空待渲染列表。此時,渲染出來的是2次setState合并后的結果,并且只會進行一次diff操作,渲染一次。
四、總結本文基于上一個版本的代碼,加入了事件處理功能,同時通過異步刷新的方法提高了渲染效率。
這是VD系列的最后一篇文章。本系列從什么是Virtual Dom這個問題出發,講解了VD的數據結構、比較方式和更新流程,并在此基礎上進行功能擴展和性能優化,支持key元素復用、自定義組件,dom事件綁定和setState異步更新。總共三百多行代碼,實現了mvvm庫的核心功能。
有關VD,如果還有什么想了解的,歡迎留言,有問必答。
P.S.: 想看完整代碼見這里,如果有必要建一個倉庫的話請留言給我:代碼
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/97573.html
摘要:現在流行的前端框架都支持自定義組件,組件化開發已經成為提高前端開發效率的銀彈。二對自定義組件的支持要想正確的渲染組件,第一步就是要告訴某個標簽是自定義組件。下面的例子里,就是一個自定義組件。解決了識別自定義標簽的問題,下一步就是定義標簽了。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、...
摘要:最后里面沒有第四個元素了,才會把蘋果從移除。四總結本文基于上一個版本的代碼,加入了對唯一標識的支持,很好的提高了更新數組元素的效率。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染...
摘要:不同的框架對這三個屬性的命名會有點差別,但表達的意思是一致的。它們分別是標簽名屬性和子元素對象。我們先來看下頁面的更新一般會經過幾個階段。元素有可能是數組的形式,需要將數組解構一層。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約...
摘要:變化的只有種更新和刪除。頁面的元素的數量隨著而變。四總結本文詳細介紹如何實現一個簡單的算法,再根據計算出的差異去更新真實的。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React 和 Vue,都不約而同的借助 Virtual DOM 技術提高頁面的渲染...
摘要:經過這次優化,計算的時間快了那么幾毫秒。基于當前這個版本的代碼還能做怎樣的優化呢,請看下一篇的內容你不知道的四的作用。 歡迎關注我的公眾號睿Talk,獲取我最新的文章:showImg(https://segmentfault.com/img/bVbmYjo); 一、前言 目前最流行的兩大前端框架,React和Vue,都不約而同的借助Virtual DOM技術提高頁面的渲染效率。那么,什...
閱讀 3006·2021-10-13 09:39
閱讀 2703·2021-09-27 13:34
閱讀 2042·2019-08-30 15:55
閱讀 3268·2019-08-30 15:43
閱讀 3647·2019-08-30 11:16
閱讀 1764·2019-08-26 18:28
閱讀 1298·2019-08-26 13:56
閱讀 925·2019-08-26 13:35