[Vue 3 从零到上线]-第三篇:网页的指挥官——指令系统 (v-if, v-for, v-bind, v-on)

[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 个功能,全部写在一个文件里简直是灾难。 在第四篇中,我们将学习 组件化思维。学会如何把网页拆成一块块"积木",让代码变得可复用、易维护。

相关推荐
李元_霸1 小时前
前端监控实践
前端·性能优化
星火开发设计1 小时前
虚析构函数:解决子类对象的内存泄漏
java·开发语言·前端·c++·学习·算法·知识
前端程序猿i1 小时前
第 7 篇:性能优化 —— 大量消息下的流畅体验
前端·vue.js·性能优化
object not found2 小时前
UniCloud 本地调试云对象报 Cannot find module ‘uni-id-common‘ 的排查与解决
前端
跨境小技2 小时前
2026 Shopee数据抓取逐步教程:技术难点、解决思路与实战方法
前端·数据库·网络爬虫
一枚小太阳2 小时前
想学 Electron?这份「能跑的示例集」一篇搞懂
前端·electron
是Dream呀2 小时前
自动化打造信息影响力:用 Web Unlocker 和 n8n 打造你的自动化资讯系统
运维·前端·爬虫·自动化
陈广亮2 小时前
告别 JSON.parse(JSON.stringify()) — 原生深拷贝 structuredClone
javascript
Trae1ounG2 小时前
这是json
前端·javascript·vue.js