從去年年底開始, Vue 3.0 的消息就不斷開始流傳,無論是官方或非官方的消息都有。 尤其在今年 (2019) 六月在 Vue.js 的作者尤雨溪在 VueConf 2019 上海的演講 (投影片 / 錄影),讓我們終於可以一窺堂奧,感受 Vue 3.0 一個比較完整的樣貌。

而其中改變最多,也是爭議最大的一個新特性,當屬 Vue 3.0 Function-based API 了。

增強對 TypeScript 的支援

如果一直以來都有追蹤 Vue.js 3.0 相關的消息,你應該知道 3.0 其中有個很重要的目標是增強對 TypeScript 的支援。

為什麼這麼強調對 TypeScript 的支援? 這就要說到早期 Vue.js 的設計,其實是沒有做類型系統 (type-system) 的設計, 而 Vue.js 的 Component 本質上就是一個「描述元件各種屬性 (options) 的物件」,這樣的好處是開發可以很容易上手,如果有寫過 Vue.js 的朋友應該很清楚這點。

但相對地缺點就是沒有了這些設計,Vue.js 要做到與工具鍊的組合以及 virtual dom、模板與 js 邏輯的映射優化,就不免處處遇到限制。

而當年為了弭平這個問題,官方選擇的解法是 Facebook 提供的 Flow 來提供 Vue.js 的類型檢查,而不是 MS 的 TypeScript。 後來的結局就是 Flow 爛尾了,TypeScript 整個生態圈越來越完整... 套句尤大的說法就是:呵呵,真香。

被廢棄的 Class API

前面說到, Vue.js 為了與 TypeScript 有更好的支援與整合, 在 2.x 時期的做法是透過 vue-class-component 來整合, 這樣的做法雖然可以做到型別系統,但在底層仍繞了很多彎,踩了不少坑。

而 Vue.js 3.0 在新版本考慮到與原有 API 的相容性、在 TypeScript 與非 TypeScript 開發者間的平衡 (看看 Angular 2+,不用 TS 無法開發)、 瀏覽器對原生 class 的支援程度、透過 class API 開發所需要的 Class fields, Decorators 等等提案還不穩定 (stage < 4) 等因素, 因此最後決定放棄 Vue 3.0 對 Class API 的路線。

Vue 3.0 Function-based API

放棄 Class API 的路線之後,取而代之的是 Function-based API。

Function-based API,又稱 composition functions:

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

可以看到,雖然捨棄了過去在 mixins 的直覺,改採 setup() 的 function 來包裝所有的共用邏輯, 但以往在 component 裡的所有 options 都可以一一找到對應。

簡單來說,它的精神是以「組合代替繼承」來實作對元件的複用。

在過去我們可能會透過 mixinsHOC (Higher-order Components) 或是 Renderless Componentsslot 來處理邏輯的抽象與複用, 但他們各自都有各自產生的問題,當你用多了之後,就容易產生像是 Namespace 衝突、 Prop 的資料來源難以追蹤,或是要產生額外的 component 實體造成性能的消耗 等。

而 Function-based API 則順利地解決了上面這些可能會遇到的問題。

關於 Vue 3.0 Function-based API 的細節,大家可以參考這份 RFC (Request for Comments, 意見徵求稿) ,英文不太好的朋友,這裡有 中文 的版本,不過要注意因為是 RFC 階段,所以隨時都還有可能會更新,而翻譯版本通常不會同步更新


雖然目前 Vue 3.0 還在 RFC 的階段,但如果你也想先試試 Function-based API 的威力的話,這裡有個為了 2.x 的版本增強的 plugin: Vue Function API

使用方式就跟過去使用其他 Vue 相關 Plugin 一樣,如果你是使用 cli 或自建 vue-loader:

npm:

$ npm install vue-function-api --save

yarn:

$ yarn add vue-function-api

然後再透過 importVue.use 的方式載入即可:

import Vue from 'vue'
import { plugin } from 'vue-function-api'

Vue.use(plugin)

詳細的內容 API 文件裡都有,有興趣的朋友可以自行參閱: https://github.com/vuejs/vue-function-api/blob/master/README.md


同時在八月份的社群聚會,我也改寫了文件上的範例,做了 V2 與 Function-based API 的簡單 demo,提供給大家做比較:

傳統 Vue 2.x 的做法

export default {
  props: {
    title: String
  },
  data() {
    return {
      todo: "",
      items: ["Vue", "is", "Awesome"]
    };
  },
  methods: {
    // Add: Click Handler Function
    add() {
      if (this.todo) {
        this.items.push(this.todo);
        this.todo = "";
      }
    },
    // Remove: Click Handler Function
    remove(item) {
      this.items = this.items.filter(v => v !== item);
    }
  },
  // mounted hook
  mounted() {
    console.log(`mounted ! ${ this.title }`);
  }
};

Function-based API 的做法

import { value, onMounted } from "vue-function-api";

export default {
  props: {
    title: String
  },
  setup(props, context) {
    // Reactive value-based variables
    const todo = value("");
    const items = value(["Vue", "is", "Awesome"]);

    // Add: Click Handler Function
    const add = () => {
      if (todo.value) {
        items.value.push(todo.value);
        todo.value = "";
      }
    };

    // Remove: Click Handler Function
    const remove = item => {
      items.value = items.value.filter(v => v !== item);
    };

    // mounted hook
    onMounted( () => {
      console.log(`onMounted ! ${ props.title }`);
    });

    return {
      todo,
      items,
      add,
      remove
    };
  }
};

可以看到新的寫法將邏輯統一包裝在 setup() function 中,對比過去以物件的形式來說,不管是在共用邏輯的管理 (現代的關注點分離:以「元件、功能」為區分,該放一起的就放一起,不像過去以 HTML / CSS / JS 來區分),在程式打包、壓縮以及 Tree-shaking 來說都有著更大的優勢。

完整原始檔: https://github.com/kurotanshi/vue-function-api-demo

同時也將活動簡報分享出來, https://speakerdeck.com/kurotanshi/chu-tan-vue-3-dot-0-function-api

對 Vue 3.0 Function-based API 有興趣的朋友不妨自行試試囉。


最後要提醒各位,Vue 3.0 雖然導入了 Function-based API 的新特性,但是原本的語法不會因此被廢棄

原本的語法不會被廢棄!

原本的語法不會被廢棄!

原本的語法不會被廢棄!

很重要所以講三次。

依然是工商服務

這是我最近在五倍紅寶石開設的 VueJS 入門課程,如果你對 VueJS 有興趣,卻總是不得其門而入,歡迎點此 https://5xruby.tw/talks/vue-js/ 報名參加。