写 Vue3 template 前,先看懂页面骨架

用一个完整 HTML 页面理解 Web 前端基础到进阶

Vue3 的 <template> 本质上就是 HTML 结构、CSS 布局、Tailwind class,再加上一些 Vue 指令。这个页面会从标签、盒子、布局讲到响应式、状态、无障碍、调试和常见组件,让你能读懂项目代码,也能开始自己写页面。

电脑屏幕上的前端代码

这张图本身也是一个 HTML 元素

<img src="..." alt="...">

HTML

负责内容

比如标题、段落、图片、按钮、输入框。

CSS

负责样式

比如颜色、字体、间距、边框、布局。

Tailwind

把 CSS 写成 class

比如 p-4、flex、items-center、rounded。

Vue3

负责数据和交互

比如 v-if、v-for、v-model、@click。

建议学习顺序

1. 结构

先看标签嵌套:谁包着谁。

2. 盒子

再看宽高、内外边距、边框。

3. 布局

重点读 Flex 和 Grid。

4. 规则

理解选择器、层叠、继承。

5. 状态

看 hover、focus、动画。

6. Vue

最后看 v-if、v-for、@click。

01

HTML 基本结构:页面的骨架

HTML 负责描述页面上有什么。你可以把一个页面想成一棵树:最外层是 <html>, 里面分成 <head><body>。 Vue3 里的 <template> 通常就对应 body 里面的一块页面结构。

一个完整 HTML 文件

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>我的页面</title>
  </head>

  <body>
    <h1>页面标题</h1>
    <p>一段正文内容。</p>
    <img src="cover.jpg" alt="封面图">
  </body>
</html>

doctype:告诉浏览器这是现代 HTML。

head:页面配置,不直接显示在页面正文里。

body:用户真正能看到的内容。

常用内容标签

<h1><h6>:标题

<p>:段落

<span>:一小段行内文字

<strong>:重要文字

<img>:图片

<button>:按钮

常用语义化布局标签

语义化标签让你一眼看出页面区域的作用,也更利于搜索引擎和无障碍阅读。

<header>:头部

<nav>:导航

<main>:主要内容

<section>:一个内容区块

<article>:独立文章或卡片

<footer>:底部

属性:写在开始标签里的额外信息

例如图片的地址、链接的跳转地址、输入框的类型、元素的 class,都是属性。

属性 常见位置 作用
class 所有标签 给 CSS 或 Tailwind 使用
id 所有标签 页面里唯一的标识
src img、script 资源地址
href a、link 跳转地址或资源地址
alt img 图片说明,图片失败时也能显示含义

属性示例

<a href="https://example.com" class="text-blue-600">
  打开网站
</a>

<img
  src="/avatar.png"
  alt="用户头像"
  class="h-12 w-12 rounded-full"
>

<input
  type="text"
  placeholder="请输入用户名"
  class="border px-3 py-2"
>

02

表单、列表、表格和媒体:真实页面最常见的内容

学会基本结构后,下一步要熟悉真实业务页面里最常见的内容:登录表单、搜索框、商品列表、数据表格、图片和视频。Vue3 项目里这些标签经常和 v-modelv-for 一起出现。

表单:label、input、select、textarea、button

表单代码

<form>
  <label>
    用户名
    <input type="text" placeholder="请输入用户名">
  </label>

  <select>
    <option>Vue3 项目开发</option>
  </select>

  <textarea placeholder="备注"></textarea>

  <button type="submit">提交</button>
</form>

关键:label 让输入项有名字,用户点击文字也能聚焦输入框。

Vue3:输入框经常写成 v-model="username"

列表

菜单、标签、消息流、商品列表都可以先理解成列表。

  • 无序列表:<ul> + <li>
  • 有序列表:<ol> + <li>
  • Vue 循环:v-for="item in list"

表格

后台管理、订单、用户列表里很常见。

标签 作用
thead 表头
tbody 表格主体
tr / th / td 行、表头格、普通格

媒体

图片用 img,视频用 video。图片一定要写有意义的 alt

<img src="/cover.jpg" alt="课程封面">

<video controls src="/demo.mp4"></video>

03

CSS 选择器、颜色和字体:找到元素并改变样子

CSS 的第一步是“选中元素”。标签选择器选一类标签,class 选择器选一组元素, id 选择器选唯一元素。Tailwind 项目里你写得少,但读旧代码或组件库时很常见。

效果:同一个卡片被不同选择器影响

选择器演示卡片

外层卡片用 class 选择器设置边框和内边距,标题用 id 选择器设置蓝色。

对应 CSS

/* 标签选择器:选中所有 p */
p {
  line-height: 1.7;
}

/* class 选择器:选中 class="card" 的元素 */
.card {
  padding: 16px;
  border: 1px solid #cbd5e1;
}

/* id 选择器:选中 id="title" 的唯一元素 */
#title {
  color: #2563eb;
}

颜色

普通 CSS 可以写颜色名、十六进制、rgb、rgba。Tailwind 用 bg-* 和 text-*。

red blue emerald slate

字体大小

text-sm:辅助说明文字

text-base:正文默认大小

text-xl:小标题

字体粗细和对齐

font-normal:普通

font-semibold:半粗

text-center + font-bold

04

层叠、继承、优先级和伪类:为什么样式会被覆盖

CSS 的难点不只是“怎么写样式”,还有“为什么这个样式生效,另一个不生效”。看懂层叠、继承和优先级后,你调样式会快很多。

优先级从低到高

标签选择器:button

范围大,优先级低。

class 选择器:.primary

项目里最常用。

id 选择器:#submit

更强,但不建议滥用。

行内样式:style="..."

非常强,维护起来不如 class 清楚。

伪类和伪元素

button:hover {
  background: #1d4ed8;
}

input:focus {
  border-color: #2563eb;
}

li:first-child {
  font-weight: 700;
}

.title::after {
  content: "new";
}

继承:文字颜色、字体通常会从父元素传给子元素。

不继承:边框、背景、宽高、margin、padding 通常不会自动继承。

后写的会覆盖先写的

选择器优先级相同时,CSS 文件里后面的规则会覆盖前面的规则。

Tailwind 的冲突

p-2 p-6 同时出现时,最终效果取决于 Tailwind 生成规则,不建议写冲突 class。

别急着用 !important

!important 会让维护变难。先检查选择器、顺序、组件样式来源。

05

盒子模型:每个元素都是一个盒子

一个盒子从里到外是 content、padding、border、margin。你看到 Tailwind 里的 w-64p-4borderm-6,基本都在控制盒子。

margin 外边距:盒子和外面元素的距离

border 边框

padding 内边距:内容和边框之间的距离

content 内容区域

HTML + Tailwind 写法

<div class="m-6 border border-blue-500 p-4">
  <p>我是内容</p>
</div>

p-4

padding: 1rem,内边距

m-6

margin: 1.5rem,外边距

w-64

width: 16rem,宽度

border

添加边框

width 和 max-width

w-full 表示占满父容器宽度,max-w-md 表示最大宽度。很多页面会用 mx-auto max-w-6xl 让内容居中并限制最大宽度。

box-sizing

现代项目常用 border-box:元素最终宽度包含 content、padding、border。 Tailwind 默认已经帮你处理好了。

margin 合并

普通 CSS 里上下相邻的 margin 有时会合并。Tailwind 项目常用 space-y-4gap-4 来更稳定地控制间距。

06

display、常用单位和溢出:布局前必须补上的底层知识

很多布局问题不是 Flex 或 Grid 写错了,而是元素本身的 display、宽度单位、内容溢出没有理解。先把这些补齐,后面的布局会顺很多。

display 的几个常见值

block:独占一行

我是 block 我也会换到下一行

inline:像文字一样排在一行

inline A inline B

hidden:不显示

Tailwind 的 hidden 等于 display: none

单位怎么选

单位 适合场景
px 边框、图标、小尺寸细节
rem 字体、间距,Tailwind 默认间距多用 rem
% 相对于父元素的宽度或高度
vw / vh 相对于浏览器窗口,常见于全屏页面

溢出处理

<div class="overflow-hidden truncate">
  一段很长很长很长的文字
</div>

w-full

宽度占满父元素。

min-h-screen

最小高度为一屏。

overflow-auto

内容超出时显示滚动。

truncate

单行文字太长时显示省略号。

07

Flex:控制子元素横排、竖排、居中和间距

Flex 主要看父元素。父元素写了 flex, 它的直接子元素就可以横向排列;再用 items-centerjustify-betweengap-4 控制位置。

效果:一张用户卡片

用户头像

Victor

正在学习 Vue3 和 Tailwind

读法:头像、文字、按钮在同一行。

关键:父元素用了 flex;中间文字用了 flex-1,所以它会占满剩余空间。

对应代码

<div class="flex items-center gap-4 p-4">
  <img class="h-14 w-14 rounded-full" src="avatar.jpg">

  <div class="flex-1">
    <h3>Victor</h3>
    <p>正在学习 Vue3</p>
  </div>

  <button class="px-4 py-2">关注</button>
</div>

flex:开启 Flex 布局

items-center:垂直居中

gap-4:子元素之间的间距

flex-1:占满剩余空间

flex-row

A B C

默认横向排列。

flex-col

A B C

纵向排列。

justify-between

主轴两端分散。

flex-wrap

标签1 标签2 标签3 标签4

空间不够时换行。

08

Grid:更适合二维布局

Flex 更像一条线,适合一行或一列;Grid 更像表格,适合同时控制行和列。 商品列表、图片墙、仪表盘卡片区,经常会用 Grid。

效果:响应式卡片网格

HTML

页面结构

CSS

页面样式

Flex

一维布局

Grid

二维布局

Tailwind

工具类

Vue3

交互逻辑

对应代码

<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
  <article class="rounded-lg bg-blue-50 p-4">HTML</article>
  <article class="rounded-lg bg-emerald-50 p-4">CSS</article>
  <article class="rounded-lg bg-orange-50 p-4">Flex</article>
</div>

grid

开启 Grid 布局。

grid-cols-3

分成三列。

col-span-2

某个元素横跨两列。

gap-4

行和列之间的间距。

09

position:把元素放到指定位置

最常见组合是父元素 relative, 子元素 absolute。 这样子元素就可以贴到父盒子的右上角、左下角、居中位置。

效果:图片上的角标

桌面上的电脑 新课程

relative + absolute

父盒子定参考,子元素定位置。

对应代码

<div class="relative overflow-hidden rounded-lg">
  <img class="h-72 w-full object-cover" src="cover.jpg">

  <span class="absolute left-4 top-4">
    新课程
  </span>

  <button class="absolute right-4 top-4">
    收藏
  </button>
</div>

relative

自己还在原位置,也给 absolute 子元素当参考点。

absolute

脱离普通文档流,按 top/right/bottom/left 定位。

fixed

固定在浏览器窗口,比如顶部导航。

z-50

层级更高,常用于弹窗、导航。

static

默认值。元素按照 HTML 顺序自然排列,不能用 top/right/bottom/left 移动。

sticky

滚动到指定位置后吸住,常用于表格头部、侧边目录。Tailwind 写法: sticky top-0

inset-0

等于 top/right/bottom/left 全部为 0,常用于遮罩层: fixed inset-0 bg-black/50

10

响应式布局:不同屏幕用不同样式

响应式的核心是:手机上先保证能看、能点;屏幕变大后再把布局展开。 Tailwind 用 sm:md:lg: 这样的前缀表示“到这个屏幕宽度后才生效”。

效果:手机上下排,电脑左右排

代码编辑器

响应式课程卡片

当前盒子默认是 flex-col,到 md 屏幕后变成 md:flex-row。 图片也从满宽变成固定宽度。

对应代码

<div class="flex flex-col gap-4 md:flex-row md:items-center">
  <img class="h-40 w-full md:h-32 md:w-48" src="code.jpg">

  <div class="flex-1">
    <h3>响应式课程卡片</h3>
    <p>手机上下排,电脑左右排。</p>
  </div>
</div>
前缀 大概含义
sm: 小平板及以上
md: 平板及以上
lg: 普通电脑屏幕及以上
xl: 更宽的桌面屏幕

11

Tailwind class:把 CSS 拆成小工具

Tailwind 不是新的 HTML 标签,它只是很多已经写好的 CSS class。你在 Vue3 里看到很长的 class="...", 可以按“尺寸、间距、颜色、布局、状态”分组读。

类型 常见 class 意思
尺寸 w-full、h-12、max-w-6xl 宽度、高度、最大宽度
间距 p-4、px-5、mt-6、gap-4 内边距、外边距、子元素间距
颜色 bg-white、text-slate-600 背景色、文字颜色
布局 flex、grid、items-center 排列方式和对齐方式
状态 hover:bg-blue-700 鼠标移上去时改变样式
响应式 md:flex-row、lg:grid-cols-2 屏幕变大后切换布局
定位 relative、absolute、top-4、z-50 控制元素叠放和位置
图片 object-cover、object-center 控制图片裁剪和对齐
溢出 overflow-hidden、truncate 隐藏超出内容或单行省略
动画 transition、duration-300、hover:scale-105 控制状态变化和动效

普通 CSS 和 Tailwind 对比

/* 普通 CSS */
.card {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 16px;
  border-radius: 8px;
  background: white;
}
<!-- Tailwind -->
<div class="flex items-center gap-4 rounded-lg bg-white p-4">
  ...
</div>

读 Tailwind class 的方法

先找布局:flexgridabsolute

再找尺寸和间距:w-fullp-4gap-4

最后看颜色和状态:bg-whitetext-slate-600hover:bg-blue-700

12

Tailwind 进阶:状态、组合、任意值和 Vue 动态 class

看真实项目时,Tailwind class 往往不只是 p-4 这种基础写法,还会有状态前缀、父子联动、任意值和条件 class。

状态前缀

group-hover:text-blue-600

鼠标移到父元素,子元素变色。

进阶写法示例

<button class="hover:bg-blue-700 focus:ring-2 disabled:opacity-50">
  保存
</button>

<div class="group">
  <p class="group-hover:text-blue-600">父元素 hover 时变色</p>
</div>

<div class="w-[320px] grid-cols-[220px_1fr]">
  任意值写法
</div>

Vue3 动态 class

<button
  class="rounded px-4 py-2 font-bold"
  :class="isActive ? 'bg-blue-600 text-white' : 'bg-slate-100 text-slate-600'"
>
  {{ isActive ? '已选中' : '未选中' }}
</button>

<div :class="{ 'opacity-50 pointer-events-none': loading }">
  加载时禁用
</div>

项目里常见的 Tailwind 读法

  • 冒号前面是条件:md:hover:focus:disabled:
  • 方括号是任意值:w-[320px]grid-cols-[220px_1fr]
  • 斜杠常表示透明度:bg-black/50 是半透明黑色。
  • 复杂 class 要分组读:布局、尺寸、间距、颜色、状态、响应式。

13

动画和过渡:让状态变化更自然

过渡 transition 用于“从一个状态平滑变到另一个状态”,比如 hover 时按钮变色。 动画 animation 用于持续播放一段关键帧,比如卡片轻微上下浮动。

效果:hover 过渡 + keyframes 动画

transition 让 hover 的颜色、阴影、位移变化更柔和。

浮动卡片

使用 @keyframes 控制上下移动。

过渡写法

<button class="transition duration-300 hover:bg-blue-700 hover:shadow-lg">
  鼠标移上来
</button>

动画写法

.float-demo {
  animation: float-card 3s ease-in-out infinite;
}

@keyframes float-card {
  0%, 100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(-10px);
  }
}

14

无障碍和语义化:让页面更专业,也更容易维护

无障碍不是额外负担,很多时候就是写正确的 HTML:按钮用 button,输入框配 label,图片写 alt,交互元素能用键盘操作。

应该这样写

打开的代码编辑器界面

常见错误和正确写法

<!-- 不推荐:div 假装按钮 -->
<div @click="submit">提交</div>

<!-- 推荐:button 天然支持键盘和语义 -->
<button type="button" @click="submit">提交</button>

<!-- 不推荐:没有说明的图片 -->
<img src="/chart.png">

<!-- 推荐:有含义的 alt -->
<img src="/chart.png" alt="本月销售趋势图">

颜色对比

浅灰字放在白底上容易看不清,正文尽量有足够对比。

键盘焦点

输入框、按钮、链接应该能 Tab 访问,并有 focus 样式。

标题层级

不要为了字号乱用 h1/h2,标题层级应该表达内容结构。

ARIA

优先用原生标签;原生标签不够时再补 aria-label、role。

Vue3 字典

15 — 26 节

Vue3 字典:从指令到组件,一翻就懂

下面这一整块都是 Vue3 的"指令字典"。每个小节都包含:源码示例 + 实时运行的真实 Vue3 实例 + 解释 & 常见场景。 页面已经引入了 Vue3 CDN,你看到的每个绿色"实时运行"框里都是一个独立的 Vue 应用。

15

Vue3 入门:3 行代码就能在 HTML 里跑起来

Vue3 不一定要 webpack。只要引入一个 CDN,就能在普通 HTML 里写响应式应用。本页 26 个示例就是这样实现的。

源码

<!-- 1. 引入 Vue3 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>

<!-- 2. 写模板 -->
<div id="app">
  <h1>{{ message }}</h1>
  <button @click="count++">点击 {{ count }} 次</button>
</div>

<!-- 3. 创建应用并挂载 -->
<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const message = ref('你好 Vue3!')
      const count = ref(0)
      return { message, count }
    }
  }).mount('#app')
</script>

运行结果

{{ message }}

点按钮,数字会变。这就是响应式。

createApp

创建一个 Vue 应用实例

setup()

写数据和函数的地方(组合式 API)

.mount('#app')

把应用挂到某个 DOM 节点上

16

响应式数据:ref 还是 reactive?

"响应式"就是:数据一变,页面跟着变。Vue3 提供两种写法:ref 包装单个值(包括对象),reactive 只能包装对象。新手建议:全部用 ref,不会错

源码

import { ref, reactive } from 'vue'

// ref:包装任意值(数字、字符串、对象、数组)
const count = ref(0)
count.value++  // JS 里要写 .value

// 模板里不用写 .value,Vue 自动解包
// <p>{{ count }}</p>

// reactive:只能包装对象/数组
const user = reactive({
  name: '小明',
  age: 18
})
user.age++  // 直接改,不用 .value

运行结果

ref 计数器

{{ count }}

reactive 用户对象

姓名:{{ user.name }}

年龄:{{ user.age }}

对比项refreactive
能包什么任意值仅对象 / 数组 / Map / Set
JS 里访问count.valueuser.name
模板里访问{{ count }}{{ user.name }}
能整个替换能(count.value = newVal不能直接 user = newObj
能解构不能(失去响应性,需 toRefs不能直接解构

17

模板插值:把数据变成页面文字

最常用的就是双花括号 {{ }}。中间可以放变量、表达式、三元运算、方法调用,不能放 if/for 语句

源码

<!-- 1. 直接插变量 -->
<p>{{ name }}</p>

<!-- 2. JS 表达式 -->
<p>{{ name.toUpperCase() }}</p>
<p>{{ count * 2 + 1 }}</p>

<!-- 3. 三元判断 -->
<p>{{ count > 5 ? '多' : '少' }}</p>

<!-- 4. v-text 同效果(覆盖整个元素) -->
<p v-text="name"></p>

<!-- 5. v-html 渲染 HTML(小心 XSS) -->
<div v-html="<b>粗体</b>"></div>

<!-- 6. v-pre:跳过编译,原样输出 -->
<p v-pre>{{ 这串不会被解析 }}</p>

运行结果

{{ name }}{{ name }}
表达式{{ name.toUpperCase() }} / {{ count * 2 + 1 }}
三元 → 数量是 {{ count > 5 ? '多' : '少' }}
v-html
v-pre{{ 这串不解析 }}

18

条件渲染 v-if / v-show:什么时候出现

v-if 是真正"删除/插入" DOM;v-show 只是 display:none 切换。频繁切换用 v-show,偶尔切换用 v-if。

源码

<div v-if="score >= 90">A 等级</div>
<div v-else-if="score >= 60">及格</div>
<div v-else>不及格</div>

<!-- v-show 永远渲染,只切换显示 -->
<div v-show="isOpen">弹窗内容</div>

<!-- 多个元素一起切换:用 template 包起来 -->
<template v-if="isLogin">
  <h1>欢迎</h1>
  <p>你已登录</p>
</template>

运行结果

{{ score }}
🏆 A 等级(优秀) ✅ 及格 ❌ 不及格
用 v-show 控制,DOM 一直存在
💬 这是一个 v-show 控制的弹窗,频繁切换时性能好。

19

列表渲染 v-for:一份模板,渲染多次

商品列表、消息列表、表格行——只要看到重复结构,几乎都是 v-for。务必加 :key,否则 Vue 会警告你。

源码

<!-- 1. 遍历数组 -->
<li v-for="item in list" :key="item.id">
  {{ item.name }} - {{ item.price }} 元
</li>

<!-- 2. 带索引 -->
<li v-for="(item, i) in list" :key="item.id">
  第 {{ i + 1 }} 个:{{ item.name }}
</li>

<!-- 3. 遍历对象 -->
<div v-for="(val, key) in obj" :key="key">
  {{ key }}: {{ val }}
</div>

<!-- 4. 遍历数字(1 到 5) -->
<span v-for="n in 5" :key="n">⭐</span>

运行结果

  • #{{ i + 1 }} {{ item.name }}

列表是空的

⭐ 重复 5 次:

⚠️ 为什么要 :key?

Vue 用 key 来识别"哪个元素是哪个"。没有 key 时,删除中间一项可能造成"复用了错位置的 DOM"——比如输入框里的值跑到了别人那里。key 要唯一且稳定,最好用 id,不要用数组索引 i

20

属性绑定 v-bind:让属性"活"起来

v-bind:src 可简写成 :src。最强大的是 :class:style,支持对象和数组语法。

源码

<!-- 1. 绑定普通属性 -->
<img :src="user.avatar" :alt="user.name">
<a :href="`/u/${user.id}`">主页</a>

<!-- 2. :class 对象语法(推荐) -->
<div :class="{ active: isActive, disabled: !canClick }">

<!-- 3. :class 数组语法 -->
<div :class="[base, isActive ? 'on' : 'off']">

<!-- 4. :style 对象语法 -->
<div :style="{ color: c, fontSize: size + 'px' }">

<!-- 5. disabled 这种布尔属性 -->
<button :disabled="loading">提交</button>

运行结果

我的样式会跟着上面的选项变化
:class = {{ JSON.stringify({ 'ring-4 ring-cyan-400': isActive, 'bg-cyan-100': isActive }) }}

21

事件处理 v-on / @:让页面会"响应"

@click="fn"v-on:click 的简写。Vue 提供了 事件修饰符(处理冒泡/默认行为)和 按键修饰符(处理键盘)。

源码

<!-- 1. 调用函数 -->
<button @click="add">+1</button>

<!-- 2. 内联表达式 -->
<button @click="count++">+1</button>

<!-- 3. 传参 -->
<button @click="add(2)">+2</button>

<!-- 4. 事件修饰符 -->
<a @click.prevent="go">不会跳转</a>
<div @click.stop="fn">不冒泡</div>
<div @click.self="fn">只自己触发</div>
<div @click.once="fn">只触发一次</div>

<!-- 5. 按键修饰符 -->
<input @keyup.enter="submit">
<input @keyup.esc="cancel">
<input @keyup.ctrl.s="save">

运行结果

"只一次"按钮点多少次都只 +1

外层(点这里也会触发,外层计数:{{ outerLog }})

  • {{ m }}

.stop

阻止冒泡

.prevent

阻止默认行为

.self

只自身能触发

.once

只触发一次

.enter / .esc / .tab

按键

.ctrl / .shift / .alt

组合键

.left / .right / .middle

鼠标按键

.passive

提高滚动性能

22

双向绑定 v-model:表单的灵魂

v-model 等于 :value + @input。改输入框 → 变量自动更新;改变量 → 输入框自动更新。

源码

<!-- 1. 文本输入 -->
<input v-model="text">

<!-- 2. 多行 -->
<textarea v-model="bio"></textarea>

<!-- 3. 单选 -->
<input type="radio" value="M" v-model="gender">
<input type="radio" value="F" v-model="gender">

<!-- 4. 复选(数组) -->
<input type="checkbox" value="读书" v-model="hobby">

<!-- 5. 下拉 -->
<select v-model="city">
  <option>北京</option>
</select>

<!-- 6. 修饰符 -->
<input v-model.lazy="x">    // 失焦才同步
<input v-model.number="age"> // 转成数字
<input v-model.trim="name">  // 去首尾空格

运行结果

{{ JSON.stringify({ name, age, gender, hobby, city }, null, 2) }}

23

计算与侦听:派生数据 & 监听变化

computed = 自动算出来的值(有缓存);watch = 数据变化时执行副作用(如发请求、写日志)。

源码

import { ref, computed, watch } from 'vue'

const price = ref(10)
const qty = ref(1)

// 自动算出 total,依赖变了就重算
const total = computed(() => price.value * qty.value)

// 监听 qty 的变化(执行副作用)
watch(qty, (newVal, oldVal) => {
  console.log(`数量从 ${oldVal} 改成 ${newVal}`)
})

// 监听多个
watch([price, qty], ([p, q]) => {
  console.log('总价:', p * q)
})

// watchEffect:自动追踪用到的依赖
watchEffect(() => {
  console.log('price 或 qty 变了', price.value, qty.value)
})

运行结果

computed 总价

¥{{ total }}

watch 日志

  • {{ log }}
对比computedwatchmethods
用途派生值副作用(请求/日志)事件处理
缓存有(依赖不变就不重算)无(每次都执行)
模板里调用{{ total }}{{ calcTotal() }}

24

生命周期钩子:组件的"出生 → 更新 → 死亡"

Vue 在组件运行的几个关键节点会"敲门"叫你写代码。最常用的就 3 个:onMounted(页面挂好了 → 发请求)、onUpdated(数据变了重新渲染后)、onUnmounted(组件消失 → 清理)。

源码

import {
  onBeforeMount, onMounted,
  onBeforeUpdate, onUpdated,
  onBeforeUnmount, onUnmounted
} from 'vue'

setup() {
  // 挂载前(DOM 还没出现)
  onBeforeMount(() => { ... })

  // 挂载后(DOM 已出现,最常用:发请求)
  onMounted(() => {
    fetch('/api/list')
  })

  // 数据变化引起更新后
  onUpdated(() => { ... })

  // 卸载前(清理 timer、解绑事件)
  onBeforeUnmount(() => {
    clearInterval(timer)
  })

  // 卸载后
  onUnmounted(() => { ... })
}

运行结果

子组件已挂载

tick = {{ tick }}

  • {{ log }}

setup()

最先执行,定义所有响应式数据

onBeforeMount

组件即将挂载到 DOM

onMounted ★最常用

DOM 已就绪 → 发起 API 请求、操作 DOM、初始化第三方库

onUpdated

数据变化引起的重新渲染完成后

onBeforeUnmount

组件即将销毁 → 清理 timer、解绑事件

onUnmounted

组件已销毁,引用清空

25

组件与插槽:把页面拆成可复用的积木

组件 = 可复用的 Vue 实例。props 父传子,emit 子传父,slot 让父决定子内部长什么样。

源码

// 子组件 MyCard.vue
<script setup>
const props = defineProps({
  title: String,
  count: { type: Number, default: 0 }
})
const emit = defineEmits(['like'])
</script>

<template>
  <div class="card">
    <h3>{{ title }}</h3>
    <!-- 默认插槽 -->
    <slot></slot>
    <!-- 具名插槽 -->
    <slot name="footer"></slot>
    <button @click="emit('like')">
      ❤ {{ count }}
    </button>
  </div>
</template>

// 父组件使用
<MyCard title="标题" :count="likes" @like="likes++">
  <p>这是默认插槽的内容</p>
  <template #footer>
    <small>脚注</small>
  </template>
</MyCard>

运行结果

户部巷里来一份精武鸭脖,麻辣过瘾。

花椒香、辣椒红,是重庆人早晨的钟声。

总点赞:{{ likes1 + likes2 }}

props(父 → 子)

父用 :title="x" 传给子,子用 defineProps 接收。子不能改 props。

emit(子 → 父)

子组件 emit('like'),父用 @like="fn" 监听。像点击事件一样。

slot(父决定内容)

默认插槽、具名插槽 #footer、作用域插槽传 prop 给父。

26

Vue3 速查表:写代码时翻这一节就够

把全部 Vue3 高频语法压缩到这一节。建议收藏,写不出来时直接 Ctrl+F 搜。

模板指令
{{ x }}插值变量、表达式
v-text替换文本同 {{ }}
v-html渲染 HTML⚠️ XSS 风险
v-if / v-else条件插入真删 DOM
v-show条件显示display 切换
v-for="i in list"循环必加 :key
v-bind:src / :src绑属性:class :style
v-on:click / @click绑事件含修饰符
v-model双向绑定表单专用
v-pre跳过编译原样输出
v-once只渲染一次性能优化
v-memo条件不变就跳过长列表优化
ref="el"DOM 引用配 useTemplateRef
事件 / 按键修饰符
.stop阻止冒泡event.stopPropagation
.prevent阻止默认form/a 标签
.self仅自身遮罩层
.capture捕获阶段极少用
.once仅一次弹窗确认
.passive不阻止默认滚动性能
.enter / .esc / .tab按键输入框
.up / .down / .left / .right方向键列表选择
.ctrl / .alt / .shift / .meta组合键快捷键
.exact完全匹配只 ctrl 不 ctrl+shift
.left / .right / .middle鼠标按键右键菜单
v-model.lazy/.number/.trimv-model 修饰表单输入
响应式 API
ref(v)包装单值.value 访问
reactive(obj)包装对象不能解构
computed(()=>...)派生值有缓存
watch(src, cb)侦听器显式声明依赖
watchEffect(cb)自动追踪立即执行
toRef / toRefs对象转 ref解构时用
readonly(x)只读包装防止意外修改
isRef / unref判断/解包通用工具
shallowRef / shallowReactive浅响应大对象性能
生命周期 + 组件 API
onMounted挂载后最常用:发请求
onUpdated更新后DOM 已变
onUnmounted卸载后清理资源
onBeforeMount / Update / Unmount对应"之前"很少用
onErrorCaptured捕获子组件错错误边界
defineProps声明 props父传子
defineEmits声明事件子传父
defineExpose暴露给父访问子方法
provide / inject跨层级传值替代 props 透传
<slot> / #name插槽默认/具名/作用域
<Teleport to>DOM 传送弹窗到 body
<Suspense>异步组件占位配 async setup
<Transition>进入/离开动画v-if/v-show 配套
<KeepAlive>缓存组件tab 切换不丢状态

🎯 读 Vue3 页面代码的总口诀

  1. 1. 先看结构:最外层是什么容器(min-h-screen / max-w-md / mx-auto)
  2. 2. 再看布局:flex / grid / absolute 谁决定排列
  3. 3. 接着看内容:img / h1 / p / button 各在哪里
  4. 4. 最后看 Vue:{{ }} 是变量,:xxx 是属性,@xxx 是事件,v-xx 是指令
  5. 5. 看不懂的 class:拆成"布局 / 盒子 / 文字 / 颜色 / 状态 / 响应式"六组

27

调试方法:用浏览器 DevTools 看懂页面为什么长这样

真正进步很快的人,不是把所有 class 背下来,而是会用浏览器检查元素、看 computed 样式、调盒子模型、切换移动端尺寸。

调样式时按这个顺序查

  1. 1. Inspect:右键检查元素,先确认你选中的是正确的 DOM 节点。
  2. 2. Styles:看哪些 CSS 规则生效,哪些被划掉。
  3. 3. Computed:看最终计算后的 width、height、padding、margin。
  4. 4. Layout:Flex/Grid 面板可以看到主轴、列、gap。
  5. 5. Responsive:切换手机宽度,检查 md/lg 前缀是否按预期生效。

遇到问题时的判断

元素没出现:先查 Vue 的 v-if、数据是否为空,再查 hiddenopacity-0

元素位置不对:先查父元素是否有 flexgridrelative

文字溢出:查宽度、min-w-0truncatebreak-words

层级被挡:z-index、定位、父元素是否创建了新的层叠上下文。

移动端崩了:查固定宽度、表格溢出、图片没有 w-fullobject-cover

28

进阶练习路线:看完以后怎么继续

学前端最好的方式是边看边改。下面这些练习覆盖真实 Vue3 项目里最常见的页面结构,按顺序做一遍,读代码和写页面都会明显顺手。

练习 1

用户资料卡

用 img、h3、p、button、flex、gap、rounded 写一张卡片。

练习 2

登录表单

写 label、input、button、focus 样式,并在 Vue 里接 v-model。

练习 3

商品列表

用 grid-cols、gap、v-for 渲染 6 张商品卡片。

练习 4

固定顶部导航

用 fixed、top-0、z-50、backdrop-blur,处理内容顶部留白。

练习 5

弹窗 Modal

用 fixed inset-0 做遮罩,用 relative/absolute 做关闭按钮。

练习 6

响应式仪表盘

手机一列,平板两列,电脑三列,练习 sm/md/lg 前缀。

读任何 Vue3 页面时的总口诀

先看 HTML 结构,再看 Tailwind 布局,再看尺寸和间距,再看颜色和状态,最后看 Vue 指令和事件。看到长 class 不要慌,把它拆成“布局、盒子、文字、颜色、状态、响应式”几组就清楚了。