傳統項目使用Vue時,為了提高性能需要修改Vue源碼,可行嗎?

首先非常感謝各位知友的鼎力相助,尤其是黑貓前輩。

我和項目經理(做Java的)溝通的時候,發現她說的子組件,並不是Vue的子組件,而是Vue組件的一個子節點(她要求所有人都使用render函數)。

公司的一個系統,公司內部使用,可以托拉拽生成頁面,生成的頁面用&引入了vue框架,並用其生成頁面。

公司自用系統

生成的頁面只有一個組件 my-tag ,根據從後台傳來的數據,決定在頁面上調用哪些render函數,渲染哪些DOM。如果在系統中修改mytag裡面的東西,就會引起組件刷新,浪費很多時間

由系統生成的頁面,也是要賣的東西

昨天把這個交給項目經理,她讓我修改watcher的機制,使得:a.b改變時,只通知子組件,不通知父組件的更新DOM部分。這需要判斷某個數據是不是父組件需要的,她讓我去網上查資料,今天下午交解決方案。

項目經理又加了需求:修改Vue第一次渲染時,VNode樹轉DOM的過程,沒生成一層VNode,渲染一層DOM

各位前輩,我自己有個思路,但項目經理堅持修改Vue源碼:

現在的情況是,組件內的東西太多,修改一個就會重新渲染組件,原因在於直接修改了mytag的props。而子節點是直接引用了props裡面的值,能不能在引用之前,把props裡面的數據拆分成多個小數據,每個小數據分別給子節點用。當跟組件的data發生變化,直接修改對應小數據,這樣,不會影響整個組件。

正在測試這個方法,說真的,我只是一個實習生,Vue原理學的只是皮毛,想盡量避免修改Vue源碼。

現在越來越感覺,這個任務,讓我發現了更多以前不曾想過的事物。


更新:題主更新了題目描述,所以答案也相應做一下變動。

  • 使用一個 render 渲染出整個 DOM 樹,則當任意變化發生時,肯定會重新 render 和 diff patch 整個組件的,這和 Watcher 機制沒什麼關係。看來你和你的項目經理根本連問題的方向都搞錯了啊,你們對 Vue 的理解比我原本想像的還要糟糕,就別老想著改 Vue 源碼了。(包括你最開始發的那張圖也是錯的,雖然在 Vue 1 裡面數據是直接綁定到 directive 的,但是你們用的是 Vue 2,Vue 2 是基於 Virtual DOM 的)
  • 幾百個節點,指的是 DOM 節點吧?如果從業務組件的維度看,一個不可能需要這麼多吧。如果一個組件由比較多的 DOM 節點組成,應該考慮如何把它抽象成一個 Vue 組件,這樣才有可能避免無效 render.
  • 每增加一個組件,應該生成一個 uuid 作為 key,不然當你插入或者刪除一個組件的時候,Vue 在 diff 的時候無法正常進行新舊匹配,造成後面大量 DOM 的更新而嚴重影響性能。
  • 每次用戶操作,都讓服務端重新下發一份新的 data,這個設計也明顯是非常有問題的。當你使用一個全新的 data 替換舊 data,即使兩個 data 長得一模一樣,也會導致組件和子組件全部重新 render(除非組件的 prop 都是基本數據類型)。
  • 以上 render 均指 vm 的 render,不表示 DOM 的更新。

以下是原答案:

你在截圖裡面提到了 shouldComponentUpdate,我就簡單說一下 Vue 和 React 在避免無效 re-render 上的一些策略。

在 React 和 Vue 的 App 中,都需要解決一個問題:即當狀態樹的一部分變化時,如何讓和這一部分狀態有關的組件重新渲染,無關的組件不渲染。

React 是默認會始終渲染的(setState 之後組件和子組件都會重新渲染),但是提供了 shouldComponentUpdate 方法讓用戶自己實現,以決定某個組件是否要重新渲染。另外 React 也提供了 PureComponent 基類,它默認實現了 shouldComponentUpdate :如果 state 或者 props 未變化則不重新渲染。

而 Vue 也本身就默認有類似 PureComponent 的特性,但是更「智能」:如果組件渲染時使用到的 data 和 props 都沒有改變,則不重新渲染。

但是 React 的 PureComponent 和 Vue 在這方面的實現原理是完全不同的:

  • PureComponent 是基於 Immutable 思想的,任何對象或數組應該看做「值」。如果要更新一個值,應該返回一個新的值,不應該直接修改原值。這樣就可以直接通過 === 判斷前後兩個值是否發生了變化。
  • Vue 則基於 getter setter 實現了一套 Reactivity System,通過 getter 建立 data 和 componentRender 之間的依賴關係,通過 setter 在修改數據之後觸發 notify 執行 componentRender. 於是當數據變化時,只有依賴了這個數據的組件才會重新渲染,非相關組件並不會重新渲染。

這是 Vue 已經實現好的特性,你不能解決或者修復一個並不存在的問題。

回到你的問題上,假如有以下 demo 代碼:

&
&
&
&
& &

&
export default {
name: "A",
data () {
return {
b: 1,
c: {name: "Jack"},
}
},
}
&

  • 當你將 data.b 修改為 2,父組件 A 和 子組件 B 都是會重新渲染的,而子組件 C 是不會重新渲染的。為什麼父組件 A 也會重新渲染?因為是 A 的渲染依賴了 data.b (B 並不依賴 data.b,B 只是依賴 props.b)
  • 而當你將 c.name 修改為 "Lucy" 時,只有組件 C 重新渲染了,因為只有 C 依賴了 c.name。

在第一種情況中,避免 A 組件重新渲染的方法是有的:把頂層組件的 data 移動到組件外部,比如使用 Vuex 的 store 中,然後子組件 B 和 C 直接從 store 中取數據,這樣 A 不依賴狀態,就不會重新渲染。

但是問題應該並非出在這裡。如我前面所述,只有父組件的渲染是多餘的,正常情況下其他兄弟組件並不會產生多餘的渲染,不應該會對性能造成明顯影響。你應該看看是不是其他地方出現了問題,比如更新數據的方式對不對,或者有沒有其他一些隱藏的地方導致大量組件出現不必要的 unmount/mount 過程,或者純粹是 App 樹結構過於龐大。

先用調試工具 profile 一下,先把問題給真正定位出來。最好是把父組件和更新數據部分的代碼貼一下。

Vue 的這套東西並不是沒有缺陷,舉幾個例子:

  • 父組件通過字面量對象給子組件傳遞屬性(&),會導致每次父組件渲染都觸發子組件渲染,理論上 Vue 本身是有辦法解決這個問題的,即在編譯期就把這種字面量轉化為 computed properties. 但是開發者完全可以避免這種情況,即自行使用 computed 代替。
  • 對於你能肯定不會變化的複雜數據結構,Vue 也會給它遍歷綁上 setter getter,這是多餘的,但是程序並不知道。對於這種類型的數據,我們可以使用 Object.feeze 把它凍結。

總之千萬別想著把 Vue 源碼改一下就能解決你的性能問題。因為首先 Vue 本身就有這種優化;其次是 Vue 已經迭代這麼久了,要是真的有這種銀彈,改幾行 Watcher 的代碼性能就蹭蹭蹭上去了,早就改了。最後是如果你真的對 Vue 細節有足夠了解,能通過改 Vue 源碼解決問題,那麼也完全可以在你的應用代碼裡面進行優化(參考我說的例子)。

現實情況往往是你改了Watcher 之後性能沒上去,BUG 倒折騰出了一堆。


以我對Vue的了解和對題主貼圖的理解,子組件的數據變動是不會引發父組件重新渲染的。

案例: JS Bin

這個jsbin中,按下toggle按鈕只會讓Child重新render,在console里只能看到child的輸出。

題主需要給出更好的reproduction,或者很有可能你的部分寫法是有問題的。(比如this.b的更新是用 this.b = newB, 而不是 this.b.m = "new m")


這個很簡單,不需要修改源碼,但是你要非常精通vue的源碼,尤其是對響應式的那一塊的細節。需要人為的為了性能寫一些耦合的組件,這在mobx的文檔里也推薦過,很簡單,就是在子組件里再解.b的引用,那麼b改變,只有子組件重渲染。但是當a增加新屬性的時候,如果調用了vue.$set方法,會使得讀取過a的組件都重渲染,這個開銷沒法避免。

更暴力的,如果你用了vuex,你直接在子組件里引用容器數據就可以了,父組件不依賴於任何對象。

而且贊同 @黑貓 的觀點,重複渲染幾次,這裡不應該有性能問題,可以排查一下是不是某些組件插入的元素過多了,考慮改一下業務機制,比如表格不要一次性渲染出來,搜索提示的那些框框控制一下數量。

此外,我覺得你們項目經理水平太差,腦子也有問題,你可以考慮跳槽了。


你和項目經理都蠻可愛的,連vue的渲染策略都還一知半解就敢說改源碼提升性能


感覺沒必要動源碼,就這個需求還沒有修源碼的必要。如果修了,那 vue 版本更新後你們怎麼 patch?


1. 典型的選擇了錯誤的架構,然後嘗試用破壞性的方案修復問題

2. 帶來問題的不是 vue 本身,而是你們組織數據的方式

3. 別把 vue 僅當個模板用

4. v=f(m)

5. 拆成幾個介面就好了


yyx:??


你把父組件傳給子組件的數據做一份copy,這樣不可以么?

記得vue官方文檔裡面是不建議直接修改prop值的。


謝謝各位知友的鼎力相助,和項目經理討論了很久,她願意把原來的DOM拆分組件了,但給了我新的任務:修改vue載入機制(給每個vm添加一個優先順序屬性,以此作為組件對應DOM掛載到DOM樹上的順序)。

我修改了appendChildren函數,並添加了一個用於在根組件 callhook(mounted) 之前執行,控制按優先順序掛載DOM的函數,完成了這個需求。

但總感覺性能不是很好,因為我對於暫時不掛載的元素,生成vm(要不怎麼獲取優先順序呢)但不生成其子vm,用&

& 作為佔位符,並保存這個dom對象到vm上,需時用replaceChild進行DOM替換。

看到vue有非同步組件機制,打算學習一下,前一陣子閱讀vue源碼,發現vue在組件的vm時,對於Vue.component的第二個參數的類型(Object or Function) 有不同的處理方法,也許可以從這裡切入。

最後再謝謝各位知友,2018,新年快樂


你們公司和項目經理也是牛逼,覺得實習生有能力可以改vue源碼

為什麼他不自己做?


為啥一個不懂技術的項目經理要指導開發怎麼去實現功能?還要讓開發去改源碼?難道他以前就是這麼乾的?知道改了源碼多少事嗎,他就這麼確定你能hold住?是不是在他眼裡這些源碼八百年也不更新升級?還有你們產品經理去哪了,一個項目經理對技術實現各種指手畫腳,最怕這種看了幾天技術文章的產品經理上來就對你技術實現指手畫腳了,要不讓他來做?


vue 8w stars. 改源碼?


連vue都沒搞懂要怎麼用就想著改源碼,6666666,趕緊換公司吧


這個問題我遇到過一個類似的,分析了一陣子才解決:記vue大型表單項目的一個性能問題

總而言之就是父組件的data數據對象被改變時,patch比對所有子元素是否需要變動,而你的子組件實在太多,所以會遍歷很多很多次。你調試下Vue.prototype._update這個方法,就會發現被調用了非常多次,上面的鏈接是我分析問題的思路,你可以參考下。

很贊同黑貓的回答,請不要修改vue本身,尤其是理解還不透徹的情況下,你最好找到導致渲染時間長的最關鍵的渲染的一步,在和項目經理理論,如果貿貿然改動了vue本身,以後想升級vue版本那就蛋疼了。


聽起來好像是想做個類似於react的shouldComponentUpdate的東西?

vue應該是不需要的。

如果實在有潔癖,通常來說這種時候我們會選擇immutablejs。

如果沒辦法說服你們可愛的項目經理,那麼不妨就照著他的想法做好了,反正你應該也不還想就這樣辭職呢。

工作中什麼人都有,果然還是自己開心最重要。


我說,你們幾個沒事能不能把這幾個bug改了


如果你們覺得性能問題是vue本身產生的,那麼就不應該選擇vue這個東西,是不是考慮一下解決問題的方式出了問題,在修改源碼的這件事情上,我感覺你所說的這位項目經理應該也沒有十分的把握搞出來吧,如果有,我建議自己搞個適合你們自己的js框架。程序員最不想動的代碼就是別人的代碼,因為大多時候都不想看。


儘可能遲地解構對象,你的需求就自然完成了

這一條不管你用react vue angular還是redux mobx vuex都適用

要組件復用就包一層數據組件


先profile看看瓶頸在哪在談解決方案吧,感覺思路是錯的。

大量DOM的繪製是瓶頸,但重繪不代表大量。另一個瓶頸是大量數據observed。

看意思像是數據量的問題,考慮將與DOM無關的數據從vue中分離出來試試。


推薦閱讀:

vue能否勝任比較大型的web應用的開發?
vuex 和 vue-router如何結合使用?
腳手架類的命令行工具用到了哪些技術?
vue.js 能否設置某個組件不被keep-alive?
vue.js 有哪些知名公司或項目用於實際生產環境了呢?

TAG:前端性能優化 | 前端框架 | Vuejs |