天天看點

打包 Composition API、Vue3

Dear,大家好,我是“前端小鑫同學”,????長期從事前端開發,安卓開發,熱衷技術,在程式設計路上越走越遠~

寫作背景:

到目前為止 Vue 為我們提供了兩種開發元件的 API 風格,選項式 API 群組合式 API。組合式 API 可以由我們導入不同的 API 函數來描述元件的邏輯,在 SFC 元件中通常還會在 script 标簽顯示标注setup來使用。

當在一個 SFC 元件中使用組合式 API 開發一段時間後你會發現,這一個元件裡面包含的了不少的功能,如何來複用這些通用的代碼塊就成了一個問題?

為了可以将這些組合式 API 實作的功能進行整合,Vue 給我們提供了一種組合式函數的概念。我們可以利用組合式 API 來打包(封裝和複用)這些有狀态邏輯的函數。

編寫組合式函數的約定:

編寫開始前需要了解一下,編寫組合式函數的約定:

  1. 命名方式:使用駝峰命名法命名,函數的字首統一使用“use”;
  2. 輸入參數:組合式函數應相容支援 ref 參數,​

    ​unref()​

    ​​可以幫助我們輕松的得到原始值。如果我們明确接收的參數存在響應式特點,我們應該使用​

    ​watch()​

    ​​或​

    ​watchEffect()​

    ​進行跟蹤。
  3. 傳回值:預設遵守傳回的結果為 ref 對象,這樣可以保證該函數在元件中解構後仍保持響應性。如果依然想通過對象.屬性的形式擷取傳回的結果,我們可以使用​

    ​reactive()​

    ​來包裝這個函數,這樣其中的 ref 會被自動解包。

實時顯示滑鼠位置:

同官方例子,我們通過監聽mousemove事件來實時擷取滑鼠的位置,并更新到頁面。

第一次使用組合式 API 開發:

下面的代碼就是我們使用組合式 API 在 SCF 元件中的實作了,其中增加了許多注釋來幫助第一次使用的夥伴了解。

<!-- 添加setup表示我們要在 SFC 元件中使用組合式 API -->
<script setup>
// 導入對應的組合式 API
import { onMounted, onUnmounted, ref } from "vue";

// 聲明一對響應式坐标
const x = ref(0);
const y = ref(0);

/**
 * 更新頁面實作的滑鼠坐标
 * @param {*} e
 */
function update(e) {
  x.value = e.pageX;
  y.value = e.pageY;
}

// 在元件挂在後添加滑鼠移動的監聽事件
onMounted(() => window.addEventListener("mousemove", update));
// 在元件寫在後移除滑鼠移動的監聽事件
onUnmounted(() => window.removeEventListener("mousemove", update));
</script>

<template>
  <p>pageX:{{ x }}</p>
  <p>pageY:{{ y }}</p>
</template>      

使用組合式函數複用功能:

為了達到上述代碼的可複用性,我們選擇使用組合式函數進行打包封裝。

  1. 建立一個mouse.js,并導出一個名為useMouse的函數;
  2. 移植 SCF 元件中script塊中的全部内容到useMouse函數(導入的依賴除外);
  3. 我們使用useMouse函數最終得到的結果應該是滑鼠的目前坐标,是以傳回 x 和 y 組成的 ref 對象組合。

完整的useMouse函數代碼如下:

// 導入對應的組合式 API
import { onMounted, onUnmounted, ref } from "vue";

export function useMouse() {
  // 聲明一對響應式坐标
  const x = ref(0);
  const y = ref(0);

  /**
  * 更新頁面實作的滑鼠坐标
  * @param {*} e
  */
  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }

  // 在元件挂在後添加滑鼠移動的監聽事件
  onMounted(() => window.addEventListener("mousemove", update));
  // 在元件寫在後移除滑鼠移動的監聽事件
  onUnmounted(() => window.removeEventListener("mousemove", update));

  return { x, y };
}      

使用useMouse函數:

<!-- 添加setup表示我們要在 SFC 元件中使用組合式 API -->
<script setup>
  import { useMouse } from "./uses/mouse";
  const { x, y } = useMouse();
</script>

<template>
<p>pageX:{{ x }}</p>
<p>pageY:{{ y }}</p>
</template>      

組合式函數中使用組合式函數:

在上面的案例中,關于事件的注冊和反注冊對于更多的場景來說抽取出來是很不錯的一件事,是以我們任然可以使用組合式函數對這一部分進行抽取,并在原來的組合式函數中使用新抽取的這個函數。

import { onMounted, onUnmounted } from "vue";

export function useEventListener(type, listener) {
  // 在元件挂在後添加滑鼠移動的監聽事件
  onMounted(() => window.addEventListener(type, listener));
  // 在元件寫在後移除滑鼠移動的監聽事件
  onUnmounted(() => window.removeEventListener(type, listener));
}      

使用的時候直接導入後使用即可:

export function useMouse() {
  ...

  // 使用useEventListener
  useEventListener("mousemove", update);

  return { x, y };
}      

異步請求資料:

我們在開發中必不可少的一個環節将是擷取伺服器提供給我們的資料,當然我們也通常會對擷取資料的工具進行封裝處理,如:封裝 axios,我們這裡通過官網的示範(fetch)來講述。

使用組合式函數編寫完的結果如下,我們在 SFC 元件中使用的時候将變得很友善。

import { ref } from "vue";

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

  fetch(url)
    .then((res) => res.json())
    .then((json) => (data.value = json))
    .catch((err) => (error.value = err));

  return { data, error };
}      

但是如果我們要根據 url 的變化來主動發起新的請求應該怎麼辦呢?也就是當傳入的 url 是一個響應式的 ref 而非原始值。

import { isRef, ref, unref, watchEffect } from "vue";

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

  function doFetch() {
    // unref 如果參數是 ref,則傳回内部值,否則傳回參數本身
    fetch(unref(url))
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err));
  }

  if (isRef(url)) {
    // watchEffect可以幫助我們立即執行傳入的函數,并響應式的進行追蹤其依賴
    watchEffect(doFetch);
  } else {
    doFetch(); // 原始值的話直接發起資料請求
  }

  return { data, error };
}      
<script setup>
import { ref } from "vue";
import { useFetch } from "./uses/fetch";
const url = ref("https://jsonplaceholder.typicode.com/todos/1");

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

function change() {
  url.value = `https://jsonplaceholder.typicode.com/todos/${Math.floor(
    Math.random() * 10 + 1
  )}`;
}
</script>

<template>
  <p>data:{{ data }}</p>
  <p>error:{{ error }}</p>
  <button @click="change">change url</button>
</template>      

寫在最後: