vue3.0的設計目標
vue3.0的設計目標
- 更小
- 更快
- 加強TypeScript支援
- 加強API設計一致性
- 提高自身可維護性
- 開放更多底層功能
具體可以從以下方面來理解
1,壓縮包體積更小
當前最小化並被壓縮的 Vue 執行時大小約為 20kB(2.6.10 版為 22.8kB)。Vue 3.0捆綁包的大小大約會減少一半,即只有10kB!
2,Object.defineProperty -> Proxy
Object.defineProperty是一個相對比較昂貴的操作,因為它直接操作物件的屬性,顆粒度比較小。將它替換為es6的Proxy,在目標物件之上架了一層攔截,代理的是物件而不是物件的屬性。這樣可以將原本對物件屬性的操作變為對整個物件的操作,顆粒度變大。
javascript引擎在解析的時候希望物件的結構越穩定越好,如果物件一直在變,可優化性降低,proxy不需要對原始物件做太多操作。
3,Virtual DOM 重構
vdom的本質是一個抽象層,用javascript描述介面渲染成什麼樣子。react用jsx,沒辦法檢測出可以優化的動態程式碼,所以做時間分片,vue中足夠快的話可以不用時間分片。
傳統vdom的效能瓶頸:
- 雖然 Vue 能夠保證觸發更新的元件最小化,但在單個元件內部依然需要遍歷該元件的整個 vdom 樹。
- 傳統 vdom 的效能跟模版大小正相關,跟動態節點的數量無關。在一些元件整個模版內只有少量動態節點的情況下,這些遍歷都是效能的浪費。
- JSX 和手寫的 render function 是完全動態的,過度的靈活性導致執行時可以用於優化的資訊不足
那為什麼不直接拋棄vdom呢?
- 高階場景下手寫 render function 獲得更強的表達力
- 生成的程式碼更簡潔
- 相容2.x
vue的特點是底層為Virtual DOM,上層包含有大量靜態資訊的模版。為了相容手寫 render function,最大化利用模版靜態資訊,vue3.0採用了動靜結合的解決方案,將vdom的操作顆粒度變小,每次觸發更新不再以元件為單位進行遍歷,主要更改如下
- 將模版基於動態節點指令切割為巢狀的區塊
- 每個區塊內部的節點結構是固定的
- 每個區塊只需要以一個 Array 追蹤自身包含的動態節點
vue3.0將 vdom 更新效能由與模版整體大小相關提升為與動態內容的數量相關
4, 更多編譯時優化
- Slot 預設編譯為函式:父子之間不存在強耦合,提升效能
- Monomorphic vnode factory:引數一致化,給它children資訊,
- Compiler-generated flags for vnode/children types
5,選用Function_based API
為什麼撤銷 Class API ?
1,更好地支援TypeScript
- Props 和其它需要注入到 this 的屬性導致型別宣告依然存在問題
- Decorators 提案的嚴重不穩定使得依賴它的方案具有重大風險
2,除了型別支援以外 Class API 並不帶來任何新的優勢
3,vue中的UI元件很少用到繼承,一般都是組合,可以用Function-based API
Function_based API示例如下
const App = { setup () { // data const count = value(0) // computed const plusOne = computed(()=>count.value + 1) // method const increment = () => {count.value++} // watch watch(() => count.value*2, v =>console.log(v)) // lifecycle onMounted(() => console.log('mounted')) // 暴露給模板或者渲染函式 return {count} } }
1,vue3.0將元件的邏輯都寫在了函式內部,setup()會取代vue2.x的data()函式,返回一個物件,暴露給模板,而且只在初始化的時候呼叫一次,因為值可以被跟蹤。
2,新的函式api:const count = value(0)
value是一個wrapper,是一個包裝物件,會包含數字0,可以用count.value來獲取這個值。在函式返回的時候會關心是value wrapper,一旦返回給模版,就不用關心了。
優點:即使count包含的是基本型別,例如數字和字串,也可以在函式之間來回傳遞,當用count.value取值的時候會觸發依賴,改值的時候會觸發更新。
3,計算屬性返回的也是這個值的包裝。
4,onMounted生命週期函式直接注入。
Function-based API 對比Class-based API有以下優點
1,對typescript更加友好,typescript對函式的引數和返回值都非常好,寫Function-based API既是javascript又是typescript,不需要任何的型別宣告,typescript可以自己做型別推導。
2,靜態的import和export是treeshaking的前提,Function-based API中的方法都是從全域性的vue中import進來的。
3,函式內部的變數名和函式名都可以被壓縮為單個字母,但是物件和類的屬性和方法名預設不被壓縮(為了防止引用出錯)。
4,更靈活的邏輯複用。
目前如果我們要在元件之間共享一些程式碼,則有兩個可用的選擇:mixins 和作用域插槽( scoped slots),但是它們都存在一些缺陷:
1,mixins 的最大缺點在於我們對它實際上新增到元件中的行為一無所知。這不僅使程式碼變得難以理解,而且還可能導致名稱與現有屬性和函式發生衝突。
2,通過使用作用域插槽,我們確切地知道可以通過 v-slot 屬性訪問了哪些屬性,因此程式碼更容易理解。這種方法的缺點是我們只能在模板中訪問它,並且只能在元件作用域內使用。
高階元件在vue中比較少,在react中引入是作為mixins的替代品,但是比mixins更糟糕,高階元件可以將多個元件進行包裝,子元件通過props接收資料,多個高階元件一起使用,不知道資料來自哪個高階元件,存在名稱空間的衝突。而且高階元件巢狀得越多,額外的元件例項就越多,造成效能損耗。
下面以一個滑鼠位置偵聽的案例演示vue3.0中的邏輯複用
new Vue({ template: '<div>Mouse position: x {{x}} / y {{y}}</div>', data () { const {x, y} = useMousePosition() return { x, y } } }) function useMousePosition () { const x = value(0) const y = value(0) const update = e => { x.value = e.pageX y.value = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return {x, y} }