Vue3.0 組合式 API 分析與實踐

本文帶大家深入理解組合式 API 的設計詳情,同時加入我們的實踐經驗總結。

 

01 背景

 

Vue3.x 版本的出現帶來了許多令人眼前一亮的新特性,其中組合式 API(Composition API),一組附加的、基於功能的 API 被作爲一種新的邏輯複用和代碼組織的方式提供給了開發者,提供更加靈活的組合組件邏輯能力。同時組合式 API 通過使用簡單的變量和函數,也提供了更好的類型推斷,這使得通過新的 API 編寫的代碼即使不用 TypeScript 也可以通過 IDE 的支持方便的獲得類型提示。組合式 API 具體的設計動機和詳情可以參考它的 RFC。本文帶大家深入理解組合式 API 的設計詳情,同時加入我們的實踐經驗總結。

 

02 簡介

組合式 API (Composition API)

 

作爲最重要的一項變動,Vue3.x 引入了組合式 API,它是一組附加的、基於功能的 API,用於靈活的組合組件邏輯。主要通過 setup 的生命週期進行組件的初始化,參考以下組合式 API 的基本使用方法:

 
<template>    <button @click="increment">        Count is: {{ state.count }}, double is: {{ state.double }}    </button></template><script>import {reactive, computed} from 'vue';
export default {    setup() {        const state = reactive({            count: 0,            double: computed(() => state.count * 2)        });        function increment() {            state.count++;        }        return {            state,            increment        };    }};</script> 

 

組合式 API 全景

組合式 API 的使用,官方參考文檔和 API 文檔有詳細說明,這裏不過多說明。爲方便大家理解和把握,根據官方的 API 文檔整理了全景圖。

Vue 組合式 API 可以分爲五大塊,涉及共計 31 個:

  • 數據響應(複雜對象):響應性 API 基礎,支持複雜對象 Object、Array 的數據響應;

  • 數據響應(內部值):內部值指 JS 內置的簡單數據結構,包括 String、Number 等;

  • computed 和 watch:基於 Ref 的數據計算與監聽;

  • 生命週期:對原生命週期封裝;

  • 其他 API:重要支持性 API;

03 設計分析

設計動機

 

邏輯複用與代碼組織

Vue 的優點在於能夠簡單快速的搭建中小型應用項目。然而隨着 Vue 如今生態的快速發展,項目的規格也變得越來越大。而受限於 Vue 當前的 API 的設計模式,不同開發者在維護同一個項目的時候,以下問題就顯露出來了:

  1. 隨着功能的逐漸豐富,複雜組件的代碼變得愈發難以理解,尤其是當開發者在閱讀其他人編寫的代碼的時候。因爲 Vue 現存的 API 使得代碼通過 options 的方式來組織,相同邏輯的代碼分散到不同的 options 裏,不符合就近原則,因此通過邏輯上就近的原則來考慮來組織代碼更加合理;

  2. 多個組件之間缺乏優雅、低成本複用邏輯的機制;現在的 Mixin 混合組件邏輯的方式,存在來源不清晰的問題;

而組合式 API 可以更加靈活的組織組件代碼,不同於 Options API,代碼可以通過特定功能來組織,也提供了組件間或者組件之外更加直接的提取、複用邏輯的能力。

更好的類型推斷

支持大型項目開發者對 TypeScript 的需求。然而 Vue 現存 API 最初的設計並沒有考慮類型推斷,大部分原因是由於 Vue 依賴 this 上下文的方式暴露屬性,這與原生 JS 中的 this 大相徑庭,而這也對當前 Vue 與 TypeScript 的整合造成了極大的困難。

目前許多 Vue 的使用者利用 vue-class-component 通過修飾符將組件以類的方式進行編寫。在設計 Vue3.x 階段,Vue 團隊嘗試提供內置的 Class API 來解決類型的問題,不過經過反覆的討論發現這種方式必須依賴修飾符,這種方式的不穩定性和不確定性導致其成爲了一個有風險的基礎建設。

相比較,組合式 API 大多使用簡單的變量和函數,類型更加友好,並且可以享受完整的類型推斷提示。使用新的 API 編寫的代碼在 TypeScript 和 JavaScript 看起來幾乎相同,因此即使不用 TypeScript ,也可以通過 IDE 的支持方便的獲得類型提示。

代碼組織

組合式 API 通過使用引入函數的方式來替代原有的 options 選項,在實例中,reactive和refs函數替代了原有的data,computed函數代替了computed屬性,watchEffect函數代替了watch屬性。乍一看所有邏輯都混合在一起寫在了 setup() 中,代碼組織還不如使用 options,但是如果我們真正考慮代碼組織的最終目標 —— 更加容易的理解代碼,我們會發現僅僅知道一個複雜的組件有哪些選項並不能幫助我們閱讀理解代碼,從而理解整個組件的代碼邏輯。當開發者們閱讀其他人編寫的組件代碼的時候,比起「組件使用了哪些選項?」,他們更在意的是「組件想要做什麼?」。

我們可以通過以下例子來對比兩者之間的區別,首先用 Options API 的寫法完成一個組件,組件邏輯很簡單,擁有兩個響應式數據 name 和 gender,兩個方法 getName 和 getGender,在調用時可以獲取到 people 對象中的 name 和 gender 屬性,代碼如下所示:

 
const component = {    data() {        return {            people: {                name: 'maxuxiao',                gender: 'male'            },            name: '',            gender: ''        };    },    methods: {        getName() {            this.name = this.people.name;        },        getGender() {            this.gender = this.people.gender;        }    }};
 

我們可以發現,如果把獲取名字和獲取性別看做兩組邏輯,把代碼以邏輯進行劃分的話,會是這樣:

相同的邏輯用相同的顏色表示。

當然組合式 API 的引入也存在一定的弊端,它在代碼組織方面提供了更多靈活性的同時,也需要開發人員通過功能分組的的方式去降低 setup 函數的複雜度,避免 setup 代碼量越來越多,return 的對象越來越複雜情況這種麪條代碼的產生。我們期望的是 setup 函數現在只是簡單地作爲調用所有組合函數的入口,參考以下功能分組的方式:

 
const component = {    setup() {        const people = {            name: 'maxuxiao',            gender: 'male'        };        return {            ...useName(people),            ...useGender(people)        };    }};const component2 = {    setup() {        const people = {            name: 'chenmingming',            gender: 'male'        };        return {            ...useName(people),            ...useGender(people)        };    }};// 處理 name 相關的業務function useName(people) {    const name = Vue.ref('');    const getName = () => {        name.value = people.name;    };    return {        name,        getName    };}// 處理 gender 相關的業務function useGender(people) {    const gender = Vue.ref('');    const getGender = () => {        gender.value = people.gender;    };    return {        gender,        getGender    };}
 

通過以上代碼我們可以看到代碼被按照業務邏輯分成了多個函數,而 setup 函數負責將它們組合起來。通過這種方式也達到了組合式 API 設計的另外一個核心目的:讓相同的代碼邏輯在不同組件中低成本的抽取和複用。不過對 Vue3.x 來說,組合式 API 並不是默認的方案,它被定義爲高級特性,意在解決大型應用程序中的複雜組件的編寫。

 

組件邏輯複用

目前業界解決組件邏輯複用和代碼組織的機制主要做法有:

  • 組合式 API:Vue

  • HOC 高階組件:React

  • Mixin 混合:React + Vue

Mixin 不論是 Vue 還是 React,現在都不太推薦使用,主要問題是來源不清晰、容易衝突、類型推導不明確等問題。

HOC 高階組件

可以看作 React 對裝飾模式的一種實現,高階組件就是一個函數,且該函數接受一個組件作爲參數,並返回一個新的組件。

 

高階組件(HOC) React 中的高級技術,用來重用組件邏輯。但高階組件本身並不是 React API。它只是一種模式,這種模式是由 React 自身的組合性質必然產生的。

 
function visible(WrappedComponent) {    return class extends Component {        render() {            const { visible, ...props } = this.props;            if (visible === false) return null;            return ;        }    }}
 

04 業界觀點

 

業界對組合式 API 存在一定的爭議,具體內容可以參考官方的討論:基於功能的組件 API 基於函數的組件 API(擴展討論)。我在這裏幫大家提煉總結下文章的觀點,大家擔心的是:

  • 原來的 options API 按 propscomputedwatchmethods生命週期等進行分組,存在一定約束,就算是低級別的程序員,也不會寫出太雜亂無章的代碼。但組合式 API,沒有這些約束,容易導致『意大利麪條式』代碼。

 

尤大對這個問題進行詳細的回覆,他的觀點,組合式 API 用於解決:

  • 複雜大型的組件複用問題;

  • 共享邏輯的組件 (Mixin,HOC 高階組件,slot);

尤大同時承認可能導致意大利麪條代碼,他建議通過代碼規範 / 指南 / CR 等方式解決。尤大沒有說過讓大家放棄使用 option API,只是說明了初衷和要解決的問題,暫時官方沒有提供最佳實踐。

 

05 項目實踐

那麼業界大家對組合式 API 落地情況怎麼樣呢?我們調研主流的幾個支持 Vue3.0 的組件庫,對組合式 API 落地使用情況分析:

  • elment-plus:全部組件使用組合式 API,未使用 Options API

  • vant:全部組件使用組合式 API,未使用 Options API

同時我們也注意到部分組件的 setup 函數,代碼量非常大,組件裏把原來的 propscomputedwatchmethods生命週期都放在 setup 中,這顯然是不合適的做法,也進一步應證了『意大利麪條』的說法。

由於現階段 Vue 團隊沒有發佈官方的最佳實踐,所以項目中是否應該使用組合式 API 來替代 Options API 就需要結合實際項目情況來考慮。在實際業務項目中,對於沒有涉及到大型應用、複雜組件以及 TS 支持的情況,組合式 API 的使用也就不是那麼必要。

經過實踐嘗試,結合 Vue 社區內的討論結果,我們決定當遇到以下幾種情況的時候,使用組合式 API,其他情況繼續保持使用 Options API:

1、邏輯複雜的大型非業務組件

2、存在大量共享邏輯的組件,包含 mixin、HOC 高階組件、slot 等場景;

3、組件涉及到多種Options或生命週期鉤子函數

 

06 總結

經過實踐,組合式 API 的出現從根本上解決了 Vue 在邏輯複用以及代碼組織上存在的問題,同時也在類型推斷上有了更好的支持,這意味着用組合式 API 編寫的代碼可以享受完整的類型推斷。另外 Vue3 也引入了 tree shaking 特性,這種按需引用 API 的使用方式可以在編譯階段將沒有用到的代碼進行 tree shaking 優化,從而有效減小項目打包體積。

同時,經過實際項目實踐,組合式 API 的引入確實解決了 Options API 在代碼組織上存在的問題,體現在:

  • 【簡潔性】極大的提升了代碼的可讀性和可維護性,尤其在開發複雜邏輯組件或者組件涉及到大量 Options 以及多種生命週期鉤子的場景;

  • 【邏輯複用】根本上解決了通過 mixin 方案複用邏輯帶來的隱式依賴,命名衝突等問題;

  • 【類型推導】入口提供的 setup 函數中,開發者也不用再依賴 this 上下文的方式暴露屬性,代碼書寫風格上也是更加精簡,可以支持 TypeScript 的類型推導;

另外,組合式 API 雖然提供了更加靈活的代碼組織能力,但是缺乏經驗的開發者對組合式API的濫用會使得代碼更加晦澀難懂。Options API 通過約定我們該在哪個位置做什麼事,一定程度上也強制我們進行了代碼分隔。而沒有正確進行邏輯分隔的組合式 API 會使 setup 中的代碼量越來越多,導致「意大利麪條代碼」情況的出現。總的來說,組合式 API 在提升了代碼質量上限的同時,也降低了下限。

 

- End -

文章看完,還不過癮?

更多精彩內容歡迎關注百度開發者中心公衆號

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章