iT邦幫忙

2022 iThome 鐵人賽

DAY 27
1
Modern Web

真的好想離開 Vue 3 新手村 feat. CompositionAPI系列 第 27

Day 27: 從 VueUse - useAsyncState 認識 Composable 和非同步處理

  • 分享至 

  • xImage
  •  

前言

會想寫這個題目有兩個原因:

  1. 想認識 composable 函式,體驗 Composition API 的強大之處,為什麼可以讓跨元件複用程式碼變得更方便。
  2. 研究「如何在元件裡處理非同步狀態」時,看到 VueUse 函式庫裡,Anthony Fu 開發的 useAsyncState 可以處理非同步狀態的問題,很好奇內部的處理邏輯。

似乎有點貪心,但這篇文章會包含這兩部份的內容。

VueUse 函式庫

先稍微介紹一下 VueUse 函式庫。

VueUse 是一個基於 Vue Composition API 開發的函式庫,也就是所謂 Composable(組合式函式)函式庫;Vue 核心團隊的 Anthony Fu 是主要開發者之一。

安裝

npm i @vueuse/core

這裡有 VueUse 所有函式列表,可以從 @vueuse/core 引入使用。


Outline

  • Compsable
  • 實作 Compsable:實作簡易版的 useMyAsyncState
  • VueUse - useAsyncState
  • 補充:Why Composable 之 mixin 的缺點

Composable

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,每次引用只要取需要的部份即可。

實作簡易版的 useMyAsyncState

在研究完 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();

自己實作簡易版 useMyAsyncState

接下來要將上面程式碼中:設定 state 初始值、執行 promise、更新 state 的部份封裝成可以複用的 useMyAsyncState,另外加上 isReadyerror 這兩個狀態,可以去監測 promise 的執行情況,或是針對錯誤進一步處理。

  • useMyAsyncState.js
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 函式回傳的物件屬性stateisReadyerror 都是具有響應式的 ref 物件RefImpl),所以都具有響應式,可以直接在模板上使用,也可以搭配 computedwatch 使用,Vue 偵測到值更新時會自動更新所有依賴。

  • RoomsView.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>

VueUse - useAsyncState

用來處理響應式的非同步狀態,不會阻塞元件的 setup 函式,並且會在 promise 完成後觸發狀態更新。

Syntax

useAsyncState(promise, initialState, options)
  • promise:回傳 promise 的非同步函式
  • option:
    • delay:延遲多少毫秒再執行 promise
    • onError:錯誤發生時觸發的 callback 函式
    • resetOnExecute:布林值,是否在執行前將 state 初始化成初始值

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>
}
  • state:狀態
  • isReady:狀態是否準備好,拿到 promise 回傳的 state
  • isLoading:整個載入過程是否結束
  • error:執行 promise 過程中如有報錯
  • execute:回傳將 promise 包好的執行函式,可以解構出來自己手動執行(例如:用戶點擊按鈕才拿資料)

使用範例

以旅館預約取得所有房間資料為例:

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 筆記


補充

Why Composable 之 mixin 的缺點

在 Vue 3 Composition API 推出之前,在 Vue Option API 要複用邏輯需要使用 Mixin,但 Mixin 有很多缺點。

這裡先簡單看一下 mixin 的用法:

  • userMixin.js
export default {
  data() {
    return {
      name: "安揪拉",
      topic: "真的好想離開 Vue 3 新手村",
    };
  },
};
  • pointMixin.js
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],
    //略
}

Mixin 的缺點

  1. 資料來源不明確
    看不出 data、computed、method 等等的來源
  2. 命名衝突問題
    如果兩個 mixin 的 data 都有 name 這個屬性,會以後者的 name 屬性為主。
  3. 邏輯之間無法有效區隔
    A 邏輯的方法可以意外改到 B 邏輯內的 state
  4. IDE 無法有效追蹤出錯位置
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?

參考資料


上一篇
Day 26: 在 Vue router - Navigation Guard 中使用 Pinia store 的小眉角
下一篇
Day 28: Quasar 新手指南 - 從安裝到使用
系列文
真的好想離開 Vue 3 新手村 feat. CompositionAPI31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言