iT邦幫忙

2024 iThome 鐵人賽

DAY 29
1
Modern Web

Vue 3 初學者:用實作帶你看過核心概念系列 第 29

Vue 3 用實作帶你看過核心概念 - Day 29:Composables(組合式函數)

  • 分享至 

  • xImage
  •  

文章背景圖

目錄

  • Composition API Composables vs Options API mixins 使用差異
  • Composables 嵌套使用 - useCounter.js 結合 useLocalStorage.js
  • Composables 非同步函數使用

Composition API Composables vs Options API mixins 使用差異

Composables是 Vue 3 中的一個重要概念,它是利用 Vue 的 Composition API 來創建和管理可重用的有狀態邏輯的函數。Composables 允許我們將組件邏輯抽象成可重用的函數,使得代碼更加模塊化和易於維護。

這邊透過計數器的案例,直接帶大家理解 Composition API composables 相對於 Options API mixins 的使用差異:

👉 Vue3 Composition API composable 計數器實作連結

在 Composition API 中,ref 用於創建響應式數據,並且需要透過 .value 來讀取和修改其值。

composables/useCounter.js:將計數器邏輯拆分到一個可組合的函數中,讓外部組件可以使用。響應式變數count以及控制數值增減的函數adddecrease都回傳為具名對象。由開發者自行決定在組件的何處應用。每次調用這個函數時,才會創建獨立的響應式ref資料,確保彼此之間不會互相影響。

composables 函數響應式資料狀態不共用

import { ref } from 'vue';

export function useCounter() {
  const count = ref(0);

  const add = () => {
    count.value += 1;
  };

  const decrease = () => {
    count.value -= 1;
  };

  return { count, add, decrease };
}

components/Counter.vue:拆分含有視圖的組件,使用解構的方式匯入具名函數,使邏輯和視圖管理分離。

<script setup>
import { useCounter } from '../composables/useCounter.js';

const { count, add, decrease } = useCounter();
</script>

<template>
  <div>
    <p>Timer Count: {{ count }}</p>
    <button @click="add">add</button>
    <button @click="decrease">decrease</button>
  </div>
</template>

App.vue:根組件匯入組件使用。

<script setup>
import Counter from './components/Counter.vue';
</script>

<template>
  <Counter />
  <Counter />
  <Counter />
</template>

Options API mixins 寫法(已不被推薦,僅用來比較差異):

👉 Vue3 Options API mixins 計數器實作連結

composables/useCounter.js:Options API 使用物件來定義各種選項,而共通邏輯的部分也會透過物件來管理和傳遞。

export const useCounter = {
   //...略
};

components/Counter.vue::視圖組件可以拆分出來,並透過mixins選項引入封裝好的計數器邏輯,實現邏輯與視圖的分離。然而,mixins在引入後,會將邏輯直接合併到原本組件的數據和方法中。當使用多個mixins時,數據或方法可能會發生覆蓋衝突,導致維護困難。

<script>
import { useCounter } from '../composables/useCounter.js';

export default {
  mixins: [useCounter],
};
</script>

<template>
  <div>
    <p>Timer Count: {{ count }}</p>
    <button @click="add">add</button>
    <button @click="decrease">decrease</button>
  </div>
</template>

Composables 嵌套使用 - useCounter.js 結合 useLocalStorage.js

這邊我們將上面的案例搭配localStrage進行本地 web 緩存。新增一個useLocalStorage.js的檔案結合前面寫的useCounter.js一起使用。

👉 Vue3 Composition API composable 計數器(嵌套使用 useLocalStorage)實作連結

點擊計數器能同步將值更新到本地緩存 web LocalStorage
點擊計數器能同步將值更新到本地緩存 LocalStorage

composables/useLocalStorage.js:結合之前的計數器邏輯,透過useCounter.js來管理count的響應式狀態,並使用watch監聽count的變化。每當count值改變時,會自動將其存儲到localStorage中,以實現持久化儲存。

import { useCounter } from './useCounter.js';
import { watch } from 'vue';

export function useLocalStorage(key, initialValue = 0) {
  const localStorageCount = parseInt(localStorage.getItem(key) || initialValue);
  const { count, add, decrease } = useCounter(localStorageCount);

  watch(count, (newVal) => {
    localStorage.setItem(key, JSON.stringify({ [key]: newVal }));
  });

  return { count, add, decrease };
}

components/Counter.vue:從父組件接收傳入的localStorage的 key 值和初始值,並將它們傳遞給 useLocalStorage composable 進行使用。

<script setup>
import { useLocalStorage } from '../composales/useLocalStorage.js';

const props = defineProps({
  inputKey: {
    default: 'testInput',
  },
  initialValue: {
    default: 0,
  },
});

const { count, add, decrease } = useLocalStorage(
  props.inputKey,
  props.initialValue
);
</script>

<template>
  <div>
    <p>Timer Count: {{ count }}</p>
    <button @click="add">add</button>
    <button @click="decrease">decrease</button>
  </div>
</template>

Composables 非同步函數使用

發送請求與錯誤處理是開發中常見且重要的場景。這個範例來自官方文件,但在實務開發中,我認為這種情況非常容易遇到,因此值得深入了解和應用。

👉 Vue3 Composition API composable 非同步函數實作連結

composables/useFetch.js:這裡使用toValue函數,使其可以接收refcomputed
及普通值。如果裡面放入refcomputed 的時候,會取得值內部值或是computed執行的結果。使這個useFetch函數能有更大程度的復用性。

為了模擬真實的網絡環境,透過timeout函數模擬請求失敗的情況。請求 API 完成後,回傳錯誤訊息和正確響應的資料,這樣組件可以根據是否發生錯誤來呈現不同的畫面。

import { ref, watchEffect, toValue } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);

  watchEffect(async () => {
    data.value = null;
    error.value = null;
    // 可接收響應式及一般資料
    const urlValue = toValue(url);

    try {
      await timeout();
      // 請求資料
      const res = await fetch(urlValue);
      data.value = await res.json();
    } catch (e) {
      // 當請求失敗的時候,把錯誤資訊傳出去
      error.value = e;
    }
  });

  return { data, error };
}

// 模擬請求成功或是失敗的情況
function timeout() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.3) {
        resolve();
      } else {
        reject(new Error('Random Error'));
      }
    }, 300);
  });
}

App.vue:這邊可以試著將 url 直接改成字串的情況下,這個useFetch的函數還是可以正常執行。根據是否有error來決定顯示錯誤訊息,並根據是否有data來顯示 API 回應的資料。

<script setup>
import { ref, computed } from 'vue';
import { useFetch } from './composables/useFetch.js';

const baseUrl = 'https://jsonplaceholder.typicode.com/todos/';
const id = ref('1');
const url = computed(() => baseUrl + id.value);

const { data, error } = useFetch(url);

const retry = () => {
  id.value = '';
  id.value = '1';
};
</script>

<template>
  Load post id:
  <button v-for="i in 5" @click="id = `${i}`">{{ i }}</button>

  <div v-if="error">
    <p>Oops! Error encountered: {{ error.message }}</p>
    <button @click="retry">Retry</button>
  </div>
  <div v-else-if="data">
    Data loaded:
    <pre>{{ data }}</pre>
  </div>
  <div v-else>Loading...</div>
</template>

總結

Composables是 Vue 3 中的重要概念,基於 Composition API 設計,為代碼重用和邏輯組織提供了更靈活、更強大的方式。相比於 Options API 的mixinsComposables具有以下優勢:

  • 更好的邏輯封裝:允許將相關功能打包成獨立、可重用的單元。
  • 更清晰的數據來源:避免了mixins可能帶來的命名衝突和數據來源不明確的問題。
  • 更靈活的組合:可以輕鬆地組合多個Composables,而不會有mixins合併順序的困擾。

通過計數器和本地存儲的例子,我們看到Composables可以嵌套使用,實現更複雜的功能組合。而非同步函數的使用示例則展示了Composables在處理 API 請求和錯誤處理等實際開發場景中的應用。

整體而言,Composables為 Vue 3 應用提供了一種更具模塊化、可維護性和靈活性的代碼組織方式,特別適合處理複雜的邏輯和狀態管理需求。


上一篇
Vue 3 用實作帶你看過核心概念 - Day 28:官方 Vue Router 的應用與技巧
下一篇
Vue 3 用實作帶你看過核心概念 - Day 30:新手村的鐵人證書 - 完賽心得
系列文
Vue 3 初學者:用實作帶你看過核心概念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言