在這篇文章中,我將講講 Vue 的 Composition API 為什么比之前的 Options API 要好,以及它是如何工作的。
Options API 有什么問題
首先,這里不是要大家放棄 Options API,如果你覺得 Options API 還不錯,已經習慣了,就繼續使用它。但我希望你能明白為什么 Composition API 是一種更好的選擇。
當我剛開始接觸 Vue 時,我喜歡它用匿名對象來構建組件的方式。簡單易學,也很自然。但隨着使用的時間越長,就會遇到一些很奇怪的問題。比如下面這段代碼:
export default {
mounted: async function () {
this.load().then(function () {
this.name = 'Foo'
})
},
}
這里的第二個this的問題我想很多人都遇到過。在不調試的情況下,很難知道兩個this為什么不一樣。我們只知道要解決這個問題,反正得用箭頭函數,例如:
mounted: async function () {
this.load().then(() => {
this.name = 'Foo';
});
}
此外,從data函數返回成員后,Vue 會包裝它們(通常使用Proxy),以便能夠監聽對象的修改。但如果你嘗試直接替換對象(比如數組),就可能監聽不到對象修改了。比如:
export default {
data: () => {
return {
items: [],
}
},
mounted: async function () {
this.load().then((result) => {
this.items = result.data
})
},
methods: {
doSomeChange() {
this.items[0].foo = 'foo'
},
},
}
這是 Vue 最常見的問題之一。盡管可以很簡單地解決這些問題,但畢竟給學習 Vue 工作原理造成了障礙。
最后,Options API 很難實現組件間的功能共享,為了解決這個問題,Vue 使用了mixins的概念。例如,你可以創建一個 mixin 來擴展組件,如下所示:
export default {
data: function () {
return { items: [] };
},
methods: {
...
}
}
mixin 看起來很像 Options API。它只是對對象進行合並,以允許你添加到特定組件上:
import dataService from "./dataServiceMixin";
export default {
mixins: [dataService],
...
mixin 最大問題是名稱沖突。
鑒於 Options API 這些不足,Composition API 誕生了。
了解 Composition API
現在來介紹一下 Composition API。Vue 2 也可以使用 Composition API。首先,你需要引入 Composition API 庫:
> npm i -s @vue/composition-api
要啟用 Composition API,你只需在main.js/ts中注冊一下:
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
讓我們來寫一個 Composition API 組件。首先,組件仍然是匿名對象:
export default {
setup() {
return {}
},
}
Composition API 的關鍵就是這個setup方法,所有所需的數據都從setup中返回。這非常類似於 Options API 的data方法:
export default {
setup() {
const name = 'Foo'
return { name }
},
}
與其只在return中賦值,不如在setup里面用一個局部變量創建。為什么要這樣做呢?因為 JavaScript 的閉包。讓我們擴展一下,再寫一個函數。
export default {
setup() {
const name = 'Foo'
function save() {
alert(`Name: ${name}`)
}
return { name, save }
},
}
該函數可以訪問name,因為它們在同一個作用域內。這就是 Composition API 的神奇之處。沒有魔術對象,只有 JavaScript。這個模式可以支持更復雜的場景,更靈活,這一切的基礎只是閉包,僅此而已。
在這個例子中name是永遠不會改變的。為了讓 Vue 能夠處理綁定,它需要知道name的變化。在這個例子中,如果你在save()中修改了代碼來改變名稱,它不會反映在 UI 中:
export default {
setup() {
const name = 'Foo'
function save() {
name = name + ' saved'
alert(`Name: ${name}`)
}
return { name, save }
},
}
響應式
要使對象具有響應式,Vue 提供了兩種方法:ref和reactive包裝器。
你可以將name包裝成一個ref對象:
import { ref } from '@vue/composition-api'
export default {
setup() {
const name = ref('Foo')
function save() {
name.value = name.value + ' saved'
alert(`Name: ${name.value}`)
}
return { name, save }
},
}
這是一個簡單的響應式,ref 對象的 value 屬性才是實際的值。
這與簡單對象可以很好地配合,但是具有自身狀態的對象(例如類或數組),對值的更改是不夠的。你真正需要的是使用一個 proxy 函數以便監聽該對象內部發生的任何更改。
import { ref, reactive } from '@vue/composition-api'
export default {
setup() {
const name = ref('Foo')
const items = reactive([])
function save() {
// Change Array
items.splice(0, items.length)
name.value = name.value + ' saved'
alert(`Name: ${name.value}`)
}
return { name, save, items }
},
}
在此示例中,使用了splice更改數組,Vue 需要了解此更改。你可以通過使用另一個稱為reactive的包裝器包裝該對象(在本例中為數組)來實現。
Composition API 中的 reactive 包裝器與 Vue2 的 Vue.observable 包裝器相同。
ref 和 react 之間的區別在於,reactive 用 proxy 包裝對象,而 ref 是簡單的值包裝器。
因此,你不需要將返回的 reactive 對象再包裝,不需要像 ref 對象一樣通過 value 屬性訪問其值。
一旦有了 ref 和 reactive 對象,就可以觀察更改。有兩種方法可以做到這一點:watch和watchEffect。watch 允許你監聽單個對象的更改:
setup() {
const name = ref("Shawn");
watch(() => name, (before, after) => {
console.log("name changes");
});
return { name };
},
該watch函數有兩個參數:將數據返回到watch的回調以及發生更改時要調用的回調。它提供兩個參數分別是修改前的值before和修改后的值after。
或者,你可以使用watchEffect 監聽 reactive 對象的任何更改。它只需要一個回調:
watchEffect(() => {
console.log('Ch..ch...ch...changes.')
})
組合組件
有時候你希望把一個已有組件的功能組合到另一個組件,以便代碼重用和數據交互。當組合組件時,Composition API 只是使用作用域和閉包解決問題。例如,你可以創建一個簡單的 service 服務:
import axios from 'axios'
export let items = []
export async function load() {
let result = await axios.get(API_URL)
items.splice(0, items.length, ...result.data)
}
export function removeItem(item) {
let index = items.indexOf(item)
if (index > -1) {
items.splice(index, 1)
}
}
然后你可以按需將需要的功能(對象或函數) import 到你的組件中:
import { ref, reactive, onMounted } from '@vue/composition-api'
import { load, items, removeItem } from './dataService'
export default {
setup() {
const name = ref('Foo')
function save() {
alert(`Name: ${this.name}`)
}
return {
load, // from import
removeItem, // from import
name,
save,
items, // from import
}
},
}
你可以使用 Composition API 更明顯式地組合你的組件。接下來我們來談談組件的使用。
使用 Props
在 Composition API 中定義 Props 的方式與 Options API 相同:
export default {
name: 'WaitCursor',
props: {
message: {
type: String,
required: false,
},
isBusy: {
type: Boolean,
required: false,
},
},
setup() {},
}
你可以通過向setup添加可選參數來訪問 Props:
setup(props) {
watch(() => props.isBusy, (b,a) => {
console.log(`isBusy Changed`);
});
}
此外,你可以添加第二個參數,可以訪問emit,slots和attrs對象,和 Options API 上 this 指針上的對象對應:
setup(props, context) {
watch(() => props.isBusy, (b,a) => context.emit("busy-changed", a));
}
使用組件
在 Vue 中有兩種使用組件的方法。你可以全局注冊組件(用作通用組件),以便可以在任何地方使用它們:
import WaitCursor from './components/WaitCursor'
Vue.component('wait-cursor', WaitCursor)
通常,你可以將組件添加到正在使用的特定組件中。在 Composition API 中,與 Options API 相同:
import WaitCursor from './components/waitCursor'
import store from './store'
import { computed } from '@vue/composition-api'
export default {
components: {
WaitCursor, // Use Component
},
setup() {
const isBusy = computed(() => store.state.isBusy)
return { isBusy }
},
}
在 Composition API 中指定了組件后,就可以使用它:
<div>
<WaitCursor message="Loading..." :isBusy="isBusy"></WaitCursor>
<div class="row">
<div class="col">
<App></App>
</div>
</div>
</div>
使用組件的方式與 Options API 中的方式沒有什么不同。
在 Vue 3 中使用 Composition API
如果你使用的是 Vue 3,則無需單獨引用 Composition API。Vue 3 已經集成好了。該@vue/composition-api庫僅用於 Vue 2 項目。Vue 3 的真正變化是,當你需要導入 Composition API 時,只需要直接從 Vue 獲取它們:
import {
ref,
reactive,
onMounted,
watch,
watchEffect,
//from "@vue/composition-api";
} from 'vue'
其他一切都一樣。只需從“ vue”導入即可。在 Vue 3 中,使用 Composition API 只是簡單一些,因為它是默認行為。
Vue 3 的主要目標之一是改善 TypeScript 體驗,所有內容都有類型庫。但是要增加類型安全性,你必須進行一些小的更改才能使用 TypeScript。在創建組件時,需使用defineComponent:
import { defineComponent, reactive, onMounted, ref } from "vue";
import Customer from "@/models/Customer";
export default defineComponent({
name: "App",
setup() {
const customers = reactive([] as Array<Customer>);
const customer = ref(new Customer());
const isBusy = ref(false);
const message = ref("");
return {
customers, customer, isBusy, message
}
}
});
在這些示例中,變量被推斷為類型實例(例如,Customers對象是Reactive<Customer[]>類型的實例)。此外,使用強類型可以降低傳遞錯誤數據的機會。當然,如果你使用 TypeScript(尤其是在 Visual Studio 或 VS Code 中),則會有非常友好的智能提示。
