简介
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 编辑过