初探 Vue 3.0 Function-based API
從去年年底開始, 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 都可以一一找到對應。
簡單來說,它的精神是以「組合代替繼承」來實作對元件的複用。
在過去我們可能會透過 mixins
、 HOC
(Higher-order Components) 或是 Renderless Components
與 slot
來處理邏輯的抽象與複用,
但他們各自都有各自產生的問題,當你用多了之後,就容易產生像是 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
然後再透過 import
與 Vue.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/ 報名參加。