HTML
负责内容
比如标题、段落、图片、按钮、输入框。
HTML
比如标题、段落、图片、按钮、输入框。
CSS
比如颜色、字体、间距、边框、布局。
Tailwind
比如 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。
第 15-26 节是 Vue3 字典,可作为速查手册随用随翻
01
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-model、
v-for
一起出现。
表单代码
<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>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 的第一步是“选中元素”。标签选择器选一类标签,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-*。
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 文件里后面的规则会覆盖前面的规则。
p-2 p-6 同时出现时,最终效果取决于 Tailwind 生成规则,不建议写冲突 class。
!important 会让维护变难。先检查选择器、顺序、组件样式来源。
05
一个盒子从里到外是 content、padding、border、margin。你看到 Tailwind 里的
w-64、
p-4、
border、
m-6,基本都在控制盒子。
margin 外边距:盒子和外面元素的距离
border 边框
padding 内边距:内容和边框之间的距离
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
添加边框
w-full 表示占满父容器宽度,max-w-md 表示最大宽度。很多页面会用
mx-auto max-w-6xl 让内容居中并限制最大宽度。
现代项目常用 border-box:元素最终宽度包含 content、padding、border。
Tailwind 默认已经帮你处理好了。
普通 CSS 里上下相邻的 margin 有时会合并。Tailwind 项目常用
space-y-4 或 gap-4 来更稳定地控制间距。
06
很多布局问题不是 Flex 或 Grid 写错了,而是元素本身的 display、宽度单位、内容溢出没有理解。先把这些补齐,后面的布局会顺很多。
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,
它的直接子元素就可以横向排列;再用
items-center、
justify-between、
gap-4
控制位置。
效果:一张用户卡片
正在学习 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
默认横向排列。
flex-col
纵向排列。
justify-between
主轴两端分散。
flex-wrap
空间不够时换行。
08
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
最常见组合是父元素 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
层级更高,常用于弹窗、导航。
默认值。元素按照 HTML 顺序自然排列,不能用 top/right/bottom/left 移动。
滚动到指定位置后吸住,常用于表格头部、侧边目录。Tailwind 写法:
sticky top-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 不是新的 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 的方法
先找布局:flex、grid、absolute。
再找尺寸和间距:w-full、p-4、gap-4。
最后看颜色和状态:bg-white、text-slate-600、hover:bg-blue-700。
12
看真实项目时,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>
md:、hover:、focus:、disabled:。w-[320px]、grid-cols-[220px_1fr]。bg-black/50 是半透明黑色。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。
15 — 26 节
下面这一整块都是 Vue3 的"指令字典"。每个小节都包含:源码示例 + 实时运行的真实 Vue3 实例 + 解释 & 常见场景。 页面已经引入了 Vue3 CDN,你看到的每个绿色"实时运行"框里都是一个独立的 Vue 应用。
15
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>
运行结果
点按钮,数字会变。这就是响应式。
createApp
创建一个 Vue 应用实例
setup()
写数据和函数的地方(组合式 API)
.mount('#app')
把应用挂到某个 DOM 节点上
16
"响应式"就是:数据一变,页面跟着变。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 }}
| 对比项 | ref | reactive |
|---|---|---|
| 能包什么 | 任意值 | 仅对象 / 数组 / Map / Set |
| JS 里访问 | count.value | user.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>
运行结果
18
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>
运行结果
19
商品列表、消息列表、表格行——只要看到重复结构,几乎都是 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>
运行结果
列表是空的
⭐ 重复 5 次:⭐
⚠️ 为什么要 :key?
Vue 用 key 来识别"哪个元素是哪个"。没有 key 时,删除中间一项可能造成"复用了错位置的 DOM"——比如输入框里的值跑到了别人那里。key 要唯一且稳定,最好用 id,不要用数组索引 i。
20
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
@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 }})
.stop
阻止冒泡
.prevent
阻止默认行为
.self
只自身能触发
.once
只触发一次
.enter / .esc / .tab
按键
.ctrl / .shift / .alt
组合键
.left / .right / .middle
鼠标按键
.passive
提高滚动性能
22
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 日志
| 对比 | computed | watch | methods |
|---|---|---|---|
| 用途 | 派生值 | 副作用(请求/日志) | 事件处理 |
| 缓存 | 有(依赖不变就不重算) | 无 | 无(每次都执行) |
| 模板里调用 | {{ 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 }}
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 高频语法压缩到这一节。建议收藏,写不出来时直接 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/.trim | v-model 修饰 | 表单输入 |
| ref(v) | 包装单值 | .value 访问 |
| reactive(obj) | 包装对象 | 不能解构 |
| computed(()=>...) | 派生值 | 有缓存 |
| watch(src, cb) | 侦听器 | 显式声明依赖 |
| watchEffect(cb) | 自动追踪 | 立即执行 |
| toRef / toRefs | 对象转 ref | 解构时用 |
| readonly(x) | 只读包装 | 防止意外修改 |
| isRef / unref | 判断/解包 | 通用工具 |
| shallowRef / shallowReactive | 浅响应 | 大对象性能 |
| 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 切换不丢状态 |
27
真正进步很快的人,不是把所有 class 背下来,而是会用浏览器检查元素、看 computed 样式、调盒子模型、切换移动端尺寸。
遇到问题时的判断
元素没出现:先查 Vue 的 v-if、数据是否为空,再查 hidden、opacity-0。
元素位置不对:先查父元素是否有 flex、grid、relative。
文字溢出:查宽度、min-w-0、truncate、break-words。
层级被挡:查 z-index、定位、父元素是否创建了新的层叠上下文。
移动端崩了:查固定宽度、表格溢出、图片没有 w-full 或 object-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
用 fixed inset-0 做遮罩,用 relative/absolute 做关闭按钮。
练习 6
手机一列,平板两列,电脑三列,练习 sm/md/lg 前缀。
先看 HTML 结构,再看 Tailwind 布局,再看尺寸和间距,再看颜色和状态,最后看 Vue 指令和事件。看到长 class 不要慌,把它拆成“布局、盒子、文字、颜色、状态、响应式”几组就清楚了。