[Vue 3 从零到上线]-第二篇:神奇的魔法盒------响应式基础 (ref 与 reactive)
一、 条件渲染:不仅仅是显示和隐藏
v-if 和 v-show 看起来效果一样,但在浏览器底层,它们的操作完全不同。
1. v-if:内存与性能的权衡
v-if 是惰性的 。如果初始条件为假,它什么也不做。只有当条件第一次变为真时,它才开始编译并创建 DOM 节点。
-
优点: 初始加载快。如果用户不点击某块区域,那块区域的代码永远不会占用浏览器的内存。
-
缺点: 切换开销大。每次显示/隐藏都会触发完整的销毁和重建过程。
2. v-show:显示权的切换
v-show 无论初始条件是什么,元素始终会被渲染并保留在 DOM 中。它只是简单地切换 CSS 的 display 属性。
-
优点: 切换极快。适合频繁点击切换的场景(如 Tab 标签、折叠面板)。
-
缺点: 初始渲染负担大。即便用户不看,浏览器也要渲染它。
typescript
<script setup lang="ts">
import { ref } from 'vue'
const isLoggedIn = ref<boolean>(false) // 登录状态
const userRole = ref<'admin' | 'user' | 'guest'>('guest') // 用户角色(使用字面量联合类型)
</script>
<template>
<div v-if="!isLoggedIn">
<button @click="isLoggedIn = true">点击登录</button>
</div>
<div v-else>
<p v-if="userRole === 'admin'">尊贵的管理员,欢迎您!</p>
<p v-else-if="userRole === 'user'">普通用户,你好。</p>
<p v-else>游客模式</p>
<button @click="isLoggedIn = false">退出</button>
</div>
</template>
💡 小白避坑指南: 如果你的组件很大、很复杂,优先选 v-if 以节省内存 ;如果是一个简单 的图标或按钮切换,选 v-show。
二、 列表渲染的"灵魂":为什么 key 这么重要?
v-for 是每个前端新手的最爱,但它隐藏着一个巨大的性能陷阱:就地复用策略。
1. 虚拟 DOM 的 Diff 算法
Vue 并不是直接操作网页,而是先在内存里生成一套虚拟网页 (Virtual DOM)。当数据变了,Vue 会对比新旧两套虚拟网页,找出最小的差异去更新。
2. Key 的作用:唯一身份证
如果你不提供 :key,Vue 在对比列表时会变得"懒惰"。它会尝试复用已经存在的 DOM 元素,而不移动它们。这会导致:
-
输入框错位: 你在第一行的输入框写了字,删掉第一行后,你会发现字出现在了原来的第二行里。
-
性能下降: Vue 无法精准定位,只好大面积刷新。
在 TS 环境下的最佳实践: 永远不要用数组的 index(索引)作为 key。一定要用数据里的 id(唯一标识符)。
typescript
<script setup lang="ts">
import { ref } from 'vue'
interface Skill {
id: number
name: string
level: string
}
const skills = ref<Skill[]>([
{ id: 1, name: 'HTML', level: '熟练' },
{ id: 2, name: 'TypeScript', level: '学习中' },
{ id: 3, name: 'Vue 3', level: '入门' }
])
</script>
<template>
<ul>
<li v-for="(item, index) in skills" :key="item.id">
{{ index + 1 }}. {{ item.name }} - 【{{ item.level }}】
</li>
</ul>
</template>
三、 属性绑定与 TS 的严谨结合
在 Vue 3 + TS 中,使用 v-bind (即 :) 时,你可以获得极强的类型检查。
1. 动态 Class 的三种写法
-
对象语法(最常用):
:class="{ 'text-red': isError }"(当 isError 为真时应用该类名)。 -
数组语法:
:class="[activeClass, errorClass]"。 -
TS 计算属性语法(最高级):
typescript
const buttonClass = computed(() => ({
'btn-primary': status.value === 'success',
'btn-danger': status.value === 'error'
}));
// 模板中使用 :class="buttonClass"
2. 样式绑定的陷阱
当你使用 :style 绑定对象时,记得使用小驼峰命名法 (如 fontSize 而不是 font-size),或者给属性名加引号。
四、 事件处理:不仅仅是点击
v-on (即 @) 承载了网页所有的交互逻辑。
1. 事件修饰符:告别 e.preventDefault()
在原生 JS 中,我们要写很多代码来阻止默认行为。Vue 将其简化成了指令后缀:
-
.prevent:提交表单不再刷新页面。 -
.stop:点击子元素不再触发父元素的点击事件(阻止冒泡)。 -
.once:事件只触发一次。
2. TS 环境下的事件类型
作为小白,你可能会遇到 event 没有类型的问题。在 TS 中,你应该显式定义它:
typescript
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement; // 类型断言
console.log(target.value);
};
五、 实战复盘:高标准的 TaskList 代码例子
下面是一个结合了 v-if, v-for, :class, @click 以及 TS 接口定义 的完整示例。
<script setup lang="ts">
import { ref } from 'vue';
// 1. 定义类型,保证数据的严谨性
interface Task {
id: number;
text: string;
isDone: boolean;
}
// 2. 响应式数据
const newTaskText = ref<string>('');
const tasks = ref<Task[]>([
{ id: 1, text: '学习 Vue 指令', isDone: true },
{ id: 2, text: '完成总结报告', isDone: false }
]);
// 3. 业务逻辑
const addTask = () => {
if (!newTaskText.value.trim()) return;
tasks.value.push({
id: Date.now(), // 简单生成一个唯一 ID
text: newTaskText.value,
isDone: false
});
newTaskText.value = ''; // 清空输入框
};
const toggleTask = (task: Task) => {
task.isDone = !task.isDone;
};
const removeTask = (id: number) => {
tasks.value = tasks.value.filter(t => t.id !== id);
};
</script>
<template>
<div class="todo-app">
<h1>我的待办清单</h1>
<div class="input-group">
<input
v-model="newTaskText"
@keyup.enter="addTask"
placeholder="输入任务按回车添加"
/>
<button @click="addTask">添加</button>
</div>
<p v-if="tasks.length === 0" class="empty">暂无任务,休息一下吧!</p>
<ul v-else>
<li
v-for="item in tasks"
:key="item.id"
:class="{ 'completed': item.isDone }"
>
<span @click="toggleTask(item)">{{ item.text }}</span>
<button @click="removeTask(item.id)">删除</button>
</li>
</ul>
</div>
</template>
<style scoped>
.completed span {
text-decoration: line-through;
color: #888;
}
.todo-app { max-width: 400px; margin: auto; }
li { display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #eee; cursor: pointer; }
.empty { color: #aaa; text-align: center; font-style: italic; }
</style>
六、 学习心法:从"搬运工"到"指挥官"
到这一篇为止,你已经可以独立写出一个具备完整交互功能的小页面了。
指令是声明式的:你只需要告诉 Vue "我要什么状态显示什么",而不需要告诉它"怎么去修改 DOM"。
数据优先:当你想删除一个列表项时,你的第一反应不应该是"删掉那个 HTML 标签",而是"删掉数组里的那个对象"。只要数据没了,视图会自动消失。
目前的 App.vue 已经越来越臃肿了。如果我们的项目有 100 个功能,全部写在一个文件里简直是灾难。 在第四篇中,我们将学习 组件化思维。学会如何把网页拆成一块块"积木",让代码变得可复用、易维护。