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