簡介
Vue.js 為開發人員提供了豐富的功能,既能加快開發速度,又能構建健壯且高性能的應用程序。
盡管這些功能有其優勢,但如果使用不當,也可能成為錯誤的根源,導致開發人員花費大量時間進行調試。錯誤不僅影響開發效率,還可能導致應用程序性能下降,最終影響 Vue 應用的整體表現。
我們可以從他人的錯誤中汲取教訓,在保證應用程序功能和性能的同時,編寫更加簡潔的代碼。
本文中,我使用 Vite 創建了一個最小化的 Vue 應用程序。你可以從這個倉庫克隆該項目。在本地安裝依賴后,運行 pnpm i
安裝依賴,并啟動開發服務器。
場景 1:在 v-for
循環中使用非唯一的 ID
該錯誤通常在以下兩種情況中出現:
為便于演示,以下是一個代碼片段:
async function fetchData() {
try {
const dataFromApi = await axios.get(
"https://dummyjson.com/recipes?page=1&limit=10&skip=10&select=name,image"
);
const recipes = dataFromApi.data.recipes as Recipe[];
favoriteRecipeList.value = [...favoriteRecipeList.value, ...recipes];
isLoading.value = false;
} catch (e) {
isLoading.value = false;
console.log(e);
}
}
該函數從源數據獲取信息,并填充到本地變量中,然后使用 v-for
循環渲染配方列表,同時考慮到 SingleRecipe
組件有自己的內部狀態。
<template>
<main class="md:min-h-screen md:p-5 p-2">
<h1 class="text-5xl">Ouch, Mistakes.</h1>
<section
v-if="isLoading"
class="w-full h-72 flex flex-col items-center justify-center"
>
<p class="text-3xl">Loading...</p>
</section>
<section
v-auto-animate
v-else
class="w-full grid md:grid-cols-3 sm:grid-cols-2 grid-cols-1 gap-3 mt-16 relative mb-14"
>
<button
:disabled="!haveRecipes"
@click="shuffleRecipe"
class="px-4 py-1.5 absolute -top-16 right-5 bg-green-700 text-white"
>
Shuffle
</button>
<SingleRecipe
v-for="(item, index) in favoriteRecipeList"
:key="index"
:recipe="item"
/>
</section>
</main>
</template>
以下是該示例的結果:
由于我們將數組的索引作為循環的 key
,一切看起來正常。當我們點擊“洗牌”按鈕時,雖然配方的順序發生了變化,但預期的過渡效果卻沒有顯示。
原因在于,每當 key
值變化時,「Vue 會使用這些 key
來強制重新渲染」。然而,由于我們使用的是數組索引作為 key
,即使列表發生變化,索引也不會改變,因此 Vue 沒有重新渲染元素,過渡效果也就沒有觸發。
為了解決這個問題,我們可以使用一個更具唯一性的值作為 key
,幫助 Vue 跟蹤列表中元素的變化:
<SingleRecipe
v-for="item in favoriteRecipeList"
:key="item.name"
:recipe="item"
/>
如圖所示,過渡效果現在已按預期正常工作。
場景 2:依賴非響應式值
在使用瀏覽器 API(如本地存儲和地理位置)時,開發人員往往需要響應式的功能。但傳統的瀏覽器 API 本身并不具備響應式,因此開發人員可能會誤用它們作為計算屬性的值。
以下是一個代碼示例,演示如何使用 ref
訪問視頻元素的屬性。
<script setup lang="ts">
import { ref, computed } from "vue";
import PlayButton from "@/components/icons/PlayButton.vue";
import PauseButton from "@/components/icons/PauseButton.vue";
const videoPlayer = ref<HTMLVideoElement>();
const playing = computed(() => !videoPlayer.value?.paused);
</script>
我們使用計算屬性來跟蹤視頻的播放狀態,并在模板中顯示對應的播放/暫停狀態。
<template>
<main class="md:min-h-screen md:p-5 p-2">
<h1 class="text-5xl">Ouch, Mistakes. - Non reactive dependency.</h1>
<section class="mt-16 relative">
<video src="/shrek_meets_donkey.mp4" ref="videoPlayer" class="mx-auto h-96" />
<div
v-auto-animate
class="inline-flex gap-3 absolute top-[48%] right-[46%]"
>
<button
@click="videoPlayer?.play()"
v-if="playing"
class="p-2 rounded-md bg-black"
>
<PlayButton />
</button>
<button
v-else
@click="videoPlayer?.pause()"
class="p-2 rounded-md bg-red-700"
>
<PauseButton />
</button>
</div>
</section>
</main>
</template>
然而,在這種情況下,控制按鈕的狀態并不是響應式的:
這是因為計算屬性依賴了一個非響應式的值。
為了解決這個問題,我們可以使用 Vueuse 庫。這個庫提供了一系列組合式 API,使瀏覽器 API 具備響應式特性,避免了重復造輪子的麻煩。
例如,我們可以使用 useMediaControls
這個組合式 API,輕松為媒體控制添加響應式支持:
<template>
<main class="md:min-h-screen md:p-5 p-2">
<h1 class="text-5xl">Ouch, Mistakes. - Non reactive dependency.</h1>
<section class="mt-16 relative">
<video ref="videoRef" class="mx-auto h-96" />
<div
v-auto-animate
class="inline-flex gap-3 absolute top-[48%] right-[46%]"
>
<button
@click="videoRef?.play()"
v-if="!videoPlaying"
class="p-2 rounded-md bg-black"
>
<PlayButton />
</button>
<button
v-else
@click="videoRef?.pause()"
class="p-2 rounded-md bg-red-700"
>
<PauseButton />
</button>
</div>
</section>
</main>
</template>
<script setup lang="ts">
import { ref, computed } from "vue";
import { useMediaControls } from "@vueuse/core";
import PlayButton from "@/components/icons/PlayButton.vue";
import PauseButton from "@/components/icons/PauseButton.vue";
const videoRef = ref();
const { playing: videoPlaying} = useMediaControls(videoRef, {
src: "/shrek_meets_donkey.mp4",
});
</script>
如預期一樣,它正常工作,因為 useMediaControls
提供了一個響應式的 ref
,可以在模板中用于顯示視頻的播放狀態。
此外,useMediaControls
還提供了其他有用的屬性,以便開發者對媒體進行更多控制。
場景 3:替換響應式值
使用 Vue 的響應式 API 時,需要特別小心,避免錯誤地替換整個對象,從而失去響應性。
以下是一個代碼示例,
<template>
<main class="md:min-h-screen md:p-5 p-2">
<h1 class="text-5xl">Ouch, Mistakes - Replacing reactive value</h1>
<section class="mt-16 relative">
<button
@click="loadMoreBirds"
class="px-4 py-1.5 absolute -top-16 right-5 bg-green-700 text-white"
>
Add more bird
</button>
<h1 class="text-3xl my-3">Swift birds in Europe</h1>
<pre>{{ arrLength }}</pre>
<h1 class="text-3xl my-3" v-if="isLoading">Loading...</h1>
<ul v-else class="list-disc pl-3">
<li v-for="item in swiftBirdsInEurope" :key="item">{{ item }}</li>
</ul>
</section>
</main>
</template>
<script setup lang="ts">
import { reactive, ref, computed } from "vue";
const isLoading = ref(false);
let swiftBirdsInEurope = reactive([
"Alpine swift",
"Chimney swift",
"Pacific swift",
]);
const arrLength = computed(() => swiftBirdsInEurope.length)
function loadMoreBirds() {
isLoading.value = true;
swiftBirdsInEurope = [
"Common swift",
"Little swift",
"Pallid swift",
"White-rumped swift",
];
setTimeout(() => (isLoading.value = false), 2000);
}
</script>
在 loadMoreBirds
函數中更新響應式值為一個新的鳥類列表。使用 Vue 開發工具檢查時,雖然值確實更新了,但計算屬性似乎沒有重新計算。
出現這個問題的原因是我們在用新數組替換響應式值時,斷開了與響應式系統的連接。
這是 Vue 響應式 API 的已知限制。響應式 API 無法追蹤已替換的對象,導致視圖未更新。
為了解決這個問題,我們建議使用 ref
來存儲數據。ref
允許數據的可變性,同時保留響應性。它還能正確存儲原始值,這是它的一個優勢。
總結
不當使用 Vue API 可能會導致意想不到的錯誤。開發人員需要在特定情況下遵循適當的 API 使用規范,確保代碼的響應性和應用的性能。
原文地址:https://medium.com/@dimeji.ogunleye20/common-vuejs-development-mistakes-10877bdc591d
該文章在 2024/11/28 17:41:21 編輯過