1. 程式人生 > 前端設計 >學習一波Vue3新特性

學習一波Vue3新特性

前言

去年前端界最轟動的事無非是React Hook的釋出,上到react-routerreact-redux等生態庫,下到React應用開發者都從class component開發方式積極擁抱Hook。那今年我認為最值得關注的是Vue3.0,目前我們可以從vue-next看到Vue3.0以及vuexvue-router等生態的開發進度,大概7、8月份Vue3.0正式版本就要落地,到時又會引起前端界的廣泛討論,這裡我整理了一波Vue3的亮點,供大家參考。

Composition API

什麼是composition api?先補一波英語知識,composition構成、作文的意思,動詞是compose

,看到compose估計很多人會想到koaredux等庫的原始碼中有一個compose核心函式,這個函式像橋樑一樣連線中介軟體(middleware)的呼叫,是構成這些優秀庫中介軟體機制的關鍵!想想compose都那麼牛,那composition api可以帶給我們什麼?能解決什麼問題?這裡我們從下面幾個現象入手,理解composition api的設計理念。

  1. 現象一:當你維護前同事長達1000多行的祖傳程式碼,為理解程式碼含義在不斷的templatedatamethods等程式碼中痛苦跳轉時

  2. 現象二:當你苦苦找不到this.xxx()方法定義,卻不敢刪掉,最終在各種mixin檔案中找到它的真身時

這個時候真的需要composition api這樣的開發方式來組織你的程式碼了。

功能分割

組合式 API 徵求意見稿中的Vue CLI UI 檔案瀏覽器為例,這個元件中有這樣幾個功能:

  • 追蹤監聽當前資料夾的狀態並展示其中的內容

  • 處理資料夾的操作(開啟、關閉、重新整理...)

  • 處理新建資料夾的建立

  • 是否只展示收藏資料夾

  • 是否只展示隱藏資料夾

  • 處理當前工作目錄的變化

就單個功能而言,它的程式碼所在位置比較分散(功能所需要的屬性在dataprops中,處理資料的方法在methods中),這勢必要求開發者在完成或閱讀程式碼時上下反覆跳轉,同一個功能的程式碼不夠聚合,那使用composition api

要怎麼寫?繼續引用組合式 API 徵求意見稿建立新資料夾程式碼例子:

function useCreateFolder(openFolder) {
  // 原來的資料 property
  const showNewFolder = ref(false)
  const newFolderName = ref('')

  // 原來的計算屬性
  const newFolderValid = computed(() => isValidMultiName(newFolderName.value))

  // 原來的一個方法
  async function createFolder() {
    if (!newFolderValid.value) return
    const result = await mutate({
      mutation: FOLDER_CREATE,variables: {
        name: newFolderName.value,},})
    openFolder(result.data.folderCreate.path)
    newFolderName.value = ''
    showNewFolder.value = false
  }

  return {
    showNewFolder,newFolderName,newFolderValid,createFolder,}
}
複製程式碼

使用composition api有幾個亮點

  • 整個函式就是一個功能
  • 函式包含建立新資料夾所依賴的資料和邏輯
  • 函式完全獨立,功能可以複用

再通過一張圖更直接的與options api進行對比:

這裡相同的色塊代表相同的功能,composition api(組合式api)讓開發者就像搭積木一樣將獨立的功能一層一層組合成一個元件,所有的邏輯和功能都一目瞭然,但基於options api不能讓我們這樣組織程式碼,造成一個功能中邏輯的分散。

邏輯複用

當兩個或多個元件的邏輯相同或相似時,在vue2.x中我們考慮用mixinHOC(高階元件,Vue較少用到)slot插槽來做邏輯複用。但這幾種方式都有各自的弊端:

  • 不知道程式碼引用來源
  • 與引入的元件屬性或方法命名衝突
  • HOCslot需要額外的有狀態的元件例項,從而使得效能有所損耗。

使用composition api能做到更清晰的邏輯複用,繼續引用組合式 API 徵求意見稿追蹤滑鼠位置的例子:

import { ref,onMounted,onUnmounted } from 'vue'

export function useMousePosition() {
  const x = ref(0)
  const y = ref(0)

  function update(e) {
    x.value = e.pageX
    y.value = e.pageY
  }

  onMounted(() => {
    window.addEventListener('mousemove',update)
  })

  onUnmounted(() => {
    window.removeEventListener('mousemove',update)
  })

  return { x,y }
}
複製程式碼

在元件中引入函式

import { useMousePosition } from './mouse'

export default {
  setup() {
    const { x,y } = useMousePosition()
    // 其他邏輯...
    return { x,y }
  },}
複製程式碼

這裡:

  • 所有的資料來源都非常清晰
  • 可以通過解構重新命名,不存在命名衝突
  • 不再需要僅為邏輯複用而建立的元件例項

告別$set

Vue2.x使用Object.defineProperty攔截資料實現響應式系統,到了Vue3.0,響應式系統的核心api使用了Proxy。我們先看Vue2.x中的一個例子

let vm = new Vue({
  data() {
    return {
        name: 'jay'
    }
  }
})

vm.age = 20 // 並不會觸發vue的響應系統
複製程式碼

Vue2.x對新增屬性是無感知的,依賴收集發生在初始化元件的過程中,Vue不能響應後來新增的屬性,這是Object.defineProperty天生的特性:

const object1 = {};
// 只能對物件上特定屬性做資料攔截
Object.defineProperty(object1,'property1',{
  get() {
    return object1['property1'];
  }
  set(value) {
    object1['property1'] = value;
  }
});
複製程式碼

Vue2.x為了讓新增的資料具有響應性,加入$set api來相容無法響應新增屬性的情況。對於Proxy api,它天生能夠攔截所有的屬性。

const object1 = {};

let p = new Proxy(object1,{
	get(target,prop,receiver) {
		
	},set(target,value) {
		console.log(target);  // {}
		console.log(prop);    // property1
		console.log(value);   // 1
		target[prop] = value;
	}
})
// 能攔截到新增的屬性
p.property1 = 1;
複製程式碼

Object.definePropertyProxy的區別:

  • Object.defineProperty針對物件上特定屬性(不能攔截新增屬性),Proxy針對handler物件(不論你是否為新增屬性)

  • Proxy除了getset外還有其他多種操作符

  • Proxy不相容IE 11,相當於IE家族不能使用Vue3.0的應用了(也許未來Vue或社群有優雅降級的方案)

有了Proxy,就不用考慮新增屬性的響應行為了,是時候要跟$set說聲再見了?。

VDom的效能優化

尤大在此前直播中表示,Vue3.0在效能優化後VDom的更新(計算diff)效能提升1.3-2倍,SSR的速度提升了2-3倍。來看看Vue3VDom優化實現。

靜態標記

<div id="app">
    <h1>我來組成靜態節點</h1>
    <p>{{name}}</p>
</div>
複製程式碼

可以在這裡檢視編譯結果:

import { createVNode as _createVNode,toDisplayString as _toDisplayString,openBlock as _openBlock,createBlock as _createBlock } from "vue"

export function render(_ctx,_cache) {
  return (_openBlock(),_createBlock("div",{ id: "app" },[
    _createVNode("h1",null,"我來組成靜態節點"),_createVNode("p",_toDisplayString(_ctx.name),1 /* TEXT */)
  ]))
}

// Check the console for the AST
複製程式碼

這裡h1是靜態節點,p為動態節點,在Vue2.x中若name發生變化,整個模板都需要重新渲染一遍,Vue3.0p標籤vdom多傳了引數1 /* TEXT */,意味著在diff時略過靜態節點,只追蹤有這引數的動態節點的更新,Vue3.0原始碼中還有其他型別的標記位:

export const enum PatchFlags {
  TEXT = 1,// 表示具有動態textContent的元素
  CLASS = 1 << 1,// 表示有動態Class的元素
  STYLE = 1 << 2,// 表示動態樣式(靜態如style="color: red",也會提升至動態)
  PROPS = 1 << 3,// 表示具有非類/樣式動態道具的元素。
  FULL_PROPS = 1 << 4,// 表示帶有動態鍵的道具的元素,與上面三種相斥
  HYDRATE_EVENTS = 1 << 5,// 表示帶有事件監聽器的元素
  STABLE_FRAGMENT = 1 << 6,// 表示其子順序不變的片段(沒懂)。 
  KEYED_FRAGMENT = 1 << 7,// 表示帶有鍵控或部分鍵控子元素的片段。
  UNKEYED_FRAGMENT = 1 << 8,// 表示帶有無key繫結的片段
  NEED_PATCH = 1 << 9,// 表示只需要非屬性補丁的元素,例如ref或hooks
  DYNAMIC_SLOTS = 1 << 10,// 表示具有動態插槽的元素
}
複製程式碼

靜態節點提升

<div id="app">
    <h1>我來組成靜態節點</h1>
    <p>{{name}}</p>
</div>
複製程式碼

編譯結果:

import { createVNode as _createVNode,createBlock as _createBlock } from "vue"

const _hoisted_1 = { id: "app" }
const _hoisted_2 = /*#__PURE__*/_createVNode("h1","我來組成靜態節點",-1 /* HOISTED */)

export function render(_ctx,_hoisted_1,[
    _hoisted_2,1 /* TEXT */)
  ]))
}

// Check the console for the AST
複製程式碼

使用靜態提升時,所有靜態節點被提升到render方法外,這表明這些節點只會在初始化中建立一次,在更新時進行復用。

新增事件快取

<div id="app">
    <p @click="onClick">hello</p>
</div>
複製程式碼

編譯結果:

import { createVNode as _createVNode,[
    _createVNode("p",{
      onClick: _cache[1] || (_cache[1] = ($event,...args) => (_ctx.onClick($event,...args)))
    },"hello")
  ]))
}

// Check the console for the AST
複製程式碼

編譯後會產生一個行內函數,使用行內函數對繫結事件做快取,更新時事件處理器沒有發生變化,則這個節點被認為一個靜態節點。

Typescript

Vue2.x是基於options(選項)的框架,也就是用object去編寫元件,但typescript是型別檢查能力範圍限於class/function,因此Vue2.x中我們使用Vue + typescript + vue-class-component + vue-property-decorator 的方式開發Vue應用。但typescriptVue3.0原始碼中佔比90%以上,Vue3.0將對tsxclass component等有更好的支援。

Tree Shaking

Vue3.0做到了按需引入,更好支援tree shaking,有時候並不需要Vue全部的功能,打包時可以將無用的程式碼剪掉

Fragment

Vue3.0支援模板新增多個根節點,意味著render函式也可以返回陣列了

結束

好了,Vue3.0的新特性還是很多的,當然該學的還是得學,別做Vue2.x釘子戶了?