會想寫這個題目有兩個原因:
似乎有點貪心,但這篇文章會包含這兩部份的內容。
先稍微介紹一下 VueUse 函式庫。
VueUse 是一個基於 Vue Composition API 開發的函式庫,也就是所謂 Composable(組合式函式)函式庫;Vue 核心團隊的 Anthony Fu 是主要開發者之一。
安裝
npm i @vueuse/core
這裡有 VueUse 所有函式列表,可以從 @vueuse/core
引入使用。
Composable 函式是基於 Vue 3 composition API 的寫法,定義一組可以跨元件複用的響應式狀態和邏輯。
類似於 Vue 2 Option API - mixin 的作用,但又解決了 mixin 的問題!(注意:官方現在不推薦使用 mixin 了,還留著只是為了向下相容)。
不是這種!(我知道這段感覺很白痴,但我一開始看到「複用邏輯」是想到這個XD)
export function sayHi() {
console.log("Hi");
}
export function checkIsPositive(num) {
return num > 0;
}
是類似這種~
function usePointCounter() {
const point = ref(0);
function plus() {
point.value++;
}
const performance = computed(() => (point.value > 100 ? "good" : "bad"));
return {
point,
plus,
performance,
};
}
透過 Composable 可以提取響應式狀態、邏輯,以供不同元件複用。
也可以想成是,能複用一整組邏輯,而邏輯裡面可以包含:資料(data)、方法(method)、計算屬性(computed)...不同的 Option,每次引用只要取需要的部份即可。
在研究完 VueUse 的 useAsyncState 之後,我發現這個 composable 函式就是:把 Day21: 來發 API 吧!Async Composition API setup() 裡提到的 Reactive Sync 方法(將非同步函式「變成」同步的響應式資料)模組化,成為可以複用的 composable 函式。
另外增加 isReady
等響應式變數,用來輔助判斷 promise 執行狀況,例如:isReady
可以拿來做 loading 元件 v-if 判斷的變數。
看不太懂沒關係,直接看 code!
在進入 VueUse 的 useAsyncState 之前,直接來實作一個簡易版的 useMyAsyncState。
所謂的 Reactive Sync 的原始作法(將非同步函式「變成」同步的響應式資料)
//inside <script setup>
const rooms = ref([]);
async function getRooms() {
try {
const { items } = await hotelAPI.GET("rooms");
rooms.value = items;
} catch (error) {
console.log(error);
}
}
getRooms();
接下來要將上面程式碼中:設定 state 初始值、執行 promise、更新 state 的部份封裝成可以複用的 useMyAsyncState,另外加上 isReady
和 error
這兩個狀態,可以去監測 promise 的執行情況,或是針對錯誤進一步處理。
import { ref } from "vue";
export function useMyAsyncState(promise, initialState) {
const state = ref(initialState);
const isReady = ref(false);
const error = ref(null);
const execute = async () => {
try {
state.value = await promise; //將 promise 回傳值賦值給 state
isReady.value = true; // 表示 state 更新完成
} catch (error) {
console.log(error);
error.value = error; // 回傳 error 物件
}
};
execute();
return { state, isReady, error };
}
從自訂 composable 函式回傳的物件屬性,state
、isReady
、error
都是具有響應式的 ref 物件(RefImpl
),所以都具有響應式,可以直接在模板上使用,也可以搭配 computed
或 watch
使用,Vue 偵測到值更新時會自動更新所有依賴。
const { state: rooms, isReady } = useMyAsyncState(
hotelAPI.GET("/rooms").then((res) => res.items),
[]
);
watch(isReady, () => console.log(`rooms 狀態更新!`, rooms));
<template>
<div v-if="!isReady" class="loading">Loading...</div>
<div v-else class="wrapper">
<h2>Rooms View</h2>
<router-link v-for="room in rooms" :to="`/room/${room.id}`" :key="room.id">
<RoomCard :room="room" class="room" />
</router-link>
</div>
</template>
用來處理響應式的非同步狀態,不會阻塞元件的 setup 函式,並且會在 promise 完成後觸發狀態更新。
useAsyncState(promise, initialState, options)
useAsyncState 還有其他 option 可以設定,useAsyncState 原始碼 裡面有列舉所有的 Options,外加註解說明設定的意思。
回傳值為物件,底下的屬性值都具有響應性。
{
state: Shallow extends true ? Ref<Data> : Ref<UnwrapRef<Data>>
isReady: Ref<boolean> //布林值
isLoading: Ref<boolean>
error: Ref<unknown>
execute: (delay?: number, ...args: any[]) => Promise<Data>
}
以旅館預約取得所有房間資料為例:
import hotelAPI from "@/api/service.js";
import { useAsyncState } from "@vueuse/core";
const { state: rooms, isReady } = useAsyncState(
hotelAPI.GET("/rooms").then((res) => res.items),
[]
);
<template>
<p>state:{{ rooms.map((room) => room.name) }}</p>
<p>isReady:{{ isReady }}</p>
<p>isLoading:{{ isLoading }}</p>
<div class="wrapper">
<h2>Rooms View</h2>
<router-link v-for="room in rooms" :to="`/room/${room.id}`" :key="room.id">
<RoomCard :room="room" class="room" />
</router-link>
</div>
</template>
畫面渲染結果
基本上 useAsyncState 的設計原理,和前面自己實作的 useMyAsyncState 是一樣的,封裝了:設定 state 初始值,在 promise 執行完成後,更新 state 整個主要流程。
但 useAsyncState 考慮了更多非同步狀態的使用情境,可以透過 options 去做調整。
例如:在 option 定義 immediate
為 false,表示要手動觸發 promise,可以搭配函式回傳的 execute
,等到需要的時機再觸發執行非同步函式。
原始碼解析實在有點佔篇幅,加上難度並不高,好奇的人可以移步到我的 hackmd 筆記。
在 Vue 3 Composition API 推出之前,在 Vue Option API 要複用邏輯需要使用 Mixin,但 Mixin 有很多缺點。
這裡先簡單看一下 mixin 的用法:
export default {
data() {
return {
name: "安揪拉",
topic: "真的好想離開 Vue 3 新手村",
};
},
};
export default {
data() {
return {
name: "Angela",
point: 0,
};
},
computed: {
performance() {
return this.point > 100 ? "good" : "bad";
},
},
methods: {
plus() {
this.point.value++;
},
},
};
Option API 風格元件
import UserMixin from './UserMixin'
import pointMixin from './PrandMixin'
export default{
mixins:[UserMixin, pointMixin],
//略
}
name
這個屬性,會以後者的 name
屬性為主。import UserMixin from './UserMixin'
import pointMixin from './PrandMixin'
export default{
mixins:[UserMixin, pointMixin],
created(){
console.log(this.name)
// 印出 Angela ...
// 但到底是從哪裡來的?
}
}
以上是 Mixin 的缺點,所以官方不推薦繼續使用 mixin,如果需要在 Option API 風格中跨元件複用邏輯,反而推薦搭配 setup()
,在裡面引用需要的 composable 函式。
可以看到 Composable 都已經沒有這些問題了,我覺得「讓元件之間的邏輯複用更清晰、方便」是推出 Composition API 風格的重要原因之一。
组合式其实是基于响应式延伸出来的一套和 Vue 生命周期绑定的一套工具。
那么组合是最重要的作用就是它可以提供可复用的逻辑,我们可以把很多的逻辑拆分出来,做成一个一个的工具。然后可以跨组件的进行复用或甚至是把它做成一个第三方库,跨应用地进行复用。这个我们会在之后进行详细的介绍。 ---Anthony Fu(滨江前端沙龙 2020)
這裡就不花時間深入探討 mixin 的限制,如果對 composable 和 mixin 的比較有興趣,可以看這篇資料:What is a Vue.js Composable?