1. 程式人生 > 實用技巧 >vue3.0的設計目標

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}
}