Vue 3 基础知识参考字典

这本字典怎么用

按用途分六组:响应式 API、模板语法、组件通信、生命周期、组合式函数、报错速查。每条四件事:签名、一句话语义(写”什么时候用、和谁的区别”)、最小示例、常见坑。字典是查的不是读的——先搜文末速查表定位,再跳回条目。

概念背景见Vue 3 语法要点,上手过程见环境搭建与第一个应用。条目默认运行在 script setup 的单文件组件里。

一、响应式 API

组内共性:响应性靠代理与读写拦截实现,任何”绕开包装直接取值”的写法都会断线。

ref

把任意值包成响应式引用,script 里读写走 .value,模板里自动解包。默认首选。

const count = ref(0)
count.value++            // 有 .value 才有追踪
const list = ref([])     // ref 深层:list.value[0].done = true 同样触发更新

坑:script 里忘写 .value 不报错——count = 1 只是换了局部绑定,界面不动,见报错速查第一条。

reactive

只对对象生效的响应式代理,字段访问不用 .value。适合聚合且不整体替换的表单类状态。

const form = reactive({ name: 'ada', page: 1 })
form.page = 2            // 直接改字段,界面更新

坑:解构、整体替换、按值传参都丢代理;只盯嵌套属性时 watch 还要 deep。与 ref 的完整对比见语法要点篇。

shallowRef

只追踪 .value 本身替换、不深层代理的 ref。大列表、第三方实例(地图、图表对象)放里面避免深层开销。

const points = shallowRef([])
points.value = fetchAll()   // 替换整个数组才触发更新
points.value.push(1)        // 嵌套变化不触发——这正是用它换来的省

坑:当成普通 ref 用(改嵌套属性)界面不更新,还以为撞了 bug。

computed

派生值,依赖自动收集、结果缓存。依赖不变不重算。

const remaining = computed(() => todos.value.filter(t => !t.done).length)
// 组件间共享:只读 computed 传下去,写操作留给声明处

坑:在 computed 里发请求、改状态——派生链里掺副作用,触发时机不可控,副作用归 watch/watchEffect。

watch

显式声明”盯谁”,默认惰性执行,回调拿到新旧值。

watch(query, (next, prev) => { search(next) }, { immediate: false })
watch(() => form.value.name, cb)   // ref 对象要盯嵌套属性:传 getter
watch(list, cb, { deep: true })     // 或整树监听

坑:ref 包对象默认浅侦听,改嵌套属性不触发;immediate: true 会把”首次执行”也接上,别和 watchEffect 混用两套心智。

watchEffect

立即执行一次,执行中读到的响应式数据自动成为依赖。适合把状态同步到 Vue 之外。

watchEffect(() => { document.title = `剩 ${remaining.value} 件` })

坑:回调用到的响应式数据全算依赖——条件分支没走到的读不到,依赖就”少收”了;拿不到旧值,需要新旧值对比就回 watch。

二、模板语法

组内共性:指令(directive)是模板里的动词,v- 前缀;缩写 : 与 @ 等价全写。

{{ }} 插值与 v-once

文本插值,跟着响应式数据变;v-once 让节点只渲染一次。

<p>{{ msg }}(提交于 {{ time }})</p>
<p v-once>版权 {{ year }}</p>   <!-- 静态内容,之后不再跟着变 -->

坑:插值输出纯文本,要注入 HTML 得用 v-html——等于放行 XSS,只能用于可信内容。

v-bind(缩写 :)

把表达式绑到属性或组件 prop 上。

<a :href="url" :class="{ active: isActive }">链接</a>
<!-- 对象语法同时管多个 class;false 的键不渲染 -->

坑::class 与静态 class 可并存,不必二选一;绑到 prop 的值是表达式不是字符串,忘写 : 传的是字面量。

v-on(缩写 @)

绑定事件,支持修饰符(modifier)串起来。

<form @submit.prevent="onSubmit">   <!-- .prevent 等价 event.preventDefault() -->
<input @keyup.enter="add" />

坑:修饰符顺序有意义,@click.stop.prevent 与 @click.prevent.stop 行为不同;组件上监听的是 emits 声明的自定义事件,不是原生 DOM 事件。

v-if 与 v-show

v-if 真正增删节点,v-show 只切 display。切换频繁用 v-show,条件很少变用 v-if。

<Detail v-if="selected" />      <!-- 不满足时组件销毁,生命周期会走卸载 -->
<span v-show="remaining">还有剩</span>

坑:v-if 与 v-for 同标签时 v-if 优先级更高,访问不到 v-for 的迭代变量——拆成 template 包一层。

v-for

列表渲染,必须绑 key。

<li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>

坑:用数组下标当 key,头部插入或删除时节点与数据错位,输入框类的局部状态会串行;数据没有 id 就造一个稳定 id。

v-model(表单)

表单控件双向绑定的语法糖::value + @input(复选框是 :checked + @change)。

<input v-model="text" />
<!-- 等价 -->
<input :value="text" @input="text = $event.target.value" />

坑:.lazy 改为 change 事件触发(失焦才同步)、.number 转数字、.trim 去首尾空格,三个修饰符按需叠加;原生组件与自定义组件的 v-model 不是一回事,见 defineModel。

三、组件通信

组内共性:数据从上往下(props),事件从下往上(emits),插槽把渲染内容的决定权交回调用方。

defineProps

声明组件的输入契约,编译器宏,无需 import。

<script setup>
defineProps({ item: { type: Object, required: true }, compact: Boolean })
</script>
<template><li :class="{ compact }">{{ item.text }}</li></template>

坑:props 只读。给 props.item.done 赋值可能”碰巧”改到父组件的对象(引用共享),父组件下一次渲染就覆盖回去,还伴随警告。

defineEmits

声明组件会抛出的事件,子组件通知父组件”该你动手了”。

<script setup>
const emit = defineEmits(['remove', 'edit'])
</script>
<template><button @click="emit('remove', item.id)">删除</button></template>

坑:事件名没声明也能抛,但父组件模板里监听的名字大小写会被浏览器统一转小写(DOM 模板场景),约定用 kebab-case 并在 emits 里列全,别靠记忆。

defineModel(3.4+)

让自定义组件直接吃 v-model 的宏,等价于 modelValue prop + update:modelValue 事件。

<script setup>
const model = defineModel()   // 支持多个:defineModel('title')
</script>
<template><input v-model="model" /></template>

坑:.value 赋值触发的是向父组件”提案”,父组件不回绑数据就不会变——双向的另一半永远在父组件手里。

provide / inject

跨层级注入,跳过中间组件逐层传 props。

// 祖先
provide('theme', themeRef)          // 传 ref 而非值,保持响应性
// 任意后代
const theme = inject('theme', ref('light'))   // 第二参给默认值

坑:注入的数据来源在组件树里不可见,重构时容易变隐式全局;只用于主题、文案、当前用户这类真·全局语义,业务数据老老实实走 props。

slot / 具名插槽 / 作用域插槽

内容分发:默认插槽放主体,具名插槽分区,作用域插槽把子组件数据递给调用方的模板。

<!-- Card.vue -->
<header><slot name="title" /></header>
<main><slot /></main>
<!-- List.vue:item 递出去 -->
<li v-for="t in todos"><slot :item="t">{{ t.text }}</slot></li>
<!-- 调用方 -->
<List v-slot="{ item }"><b :class="{ done: item.done }">{{ item.text }}</b></List>

坑:作用域插槽解构出的 item 是快照语义的错觉——它仍是响应式代理,但别在里面再改它,改数据的路径应该走事件。

四、生命周期

组内共性:script setup 本身覆盖了 beforeCreate/created;常用的只有挂载后的 onMounted 与卸载时的 onUnmounted。

onMounted

DOM 就绪后执行:发首个请求、初始化图表、聚焦输入框。

onMounted(() => { chartRef.value?.init(); fetchList() })

坑:在 setup 顶层直接取 ref 拿到 null——此时还没挂载;也不要在 onMounted 里改一大片状态导致”首屏闪一下”,初始化数据尽量在声明处就绪。

onUnmounted

组件卸载后执行,清理定时器、事件监听、订阅。

const timer = setInterval(poll, 5000)
onUnmounted(() => clearInterval(timer))   // 注册与清理成对出现

坑: setInterval、addEventListener、WebSocket 忘了清理,组件销毁后回调还在引用旧状态,报错或泄漏都在远处炸。

onUpdated 与其余钩子

更新后执行,低频使用;想对数据变化做反应,watch 通常是更准的工具。

onUpdated(() => { saveScrollPos() })   // 任何响应式更新都会走这里,容易放大成循环

坑:onUpdated 里再改状态可能造成”更新触发更新”,边界失控;调试用它看”渲染了什么”可以,业务逻辑别压在上面。

五、组合式函数

组内共性:use 前缀命名、返回 ref/函数、每次调用独立一份状态——逻辑复用的单位。

useXxx 组合式函数

把”状态 + 操作它的函数”打包导出,跨组件复用逻辑而不共享状态。

// useCounter.js
export function useCounter(initial = 0) {
  const count = ref(initial)
  const inc = () => count.value++
  return { count, inc }   // 返回 ref,响应性才带得出去
}
// 两个组件分别调用,各拿各的 count
const { count, inc } = useCounter(10)

坑:返回解构后的普通值(return count.value)响应性当场死亡;模块顶层直接写状态则是”全局单例”,所有调用方共享,和组件级复用是两回事,要共享就去用 Pinia。

六、常见报错速查

组内共性:八成不是”Vue 的 bug”,而是踩了两类经典反模式——绕开响应式包装直接取值,或绕开单向数据流直接改别人的数据。先对症状,再回上文找对应条目。

症状 真实原因 修法
改了值界面没反应 script 里忘写 .value(count = 1 只是换局部绑定) 改 count.value++;DevTools 面板先确认数据变没变
reactive 解构后不更新 解构拿到的是普通值,代理丢了 toRefs(form) 再解构,或改用 ref 声明
watch 监听对象内部变化不触发 ref 包对象默认浅侦听 传 getter 盯具体属性,或 { deep: true }
列表增删后输入框内容串行 v-for 用下标当 key :key 绑稳定业务 id
改 props 收到警告 单向数据流:props 属于父组件 emit 事件让父组件改,或 defineModel
组件销毁后还在发请求/报错 副作用没在 onUnmounted 清理 定时器、监听、订阅与注册成对出现
v-if 里用 v-for 的变量报 undefined 同标签时 v-if 优先级更高 套 template 分层写
父组件 v-model 传了但不生效 子组件没声明 modelValue/update:modelValue defineModel()(3.4+)或手写 prop + emit

速查表

条目 一句话用途 有什么坑
ref 任意值的响应式包装,默认首选 忘 .value 静默失败
reactive 对象代理,免 .value 解构/替换即失联
shallowRef 只盯整体替换的省开销 ref 嵌套改不触发
computed 带缓存的派生值 里面别掺副作用
watch 显式侦听,拿新旧值 ref 对象默认浅侦听
watchEffect 立即执行、自动收依赖 拿不到旧值,分支影响收集
{{ }} / v-once 文本插值 / 一次性渲染 v-html 放行 XSS
v-bind(:) 绑属性与 prop 忘冒号传成字符串
v-on(@) 绑事件与修饰符 修饰符顺序有语义
v-if / v-show 增删节点 / 切 display 与 v-for 同标签优先级坑
v-for 列表渲染 下标当 key 会串位
v-model 表单双向绑定(语法糖) 原生与组件版不是一回事
defineProps 声明输入契约 只读,改了警告
defineEmits 声明输出事件 不声明也能抛,靠约定兜底
defineModel 组件吃 v-model(3.4+) 另一半在父组件手里
provide/inject 跨层级注入 隐式全局,慎用于业务数据
slot 三兄弟 内容分发 作用域插槽里别改数据
onMounted DOM 就绪初始化 顶层取 ref 是 null
onUnmounted 卸载清理 忘清理在远处炸
onUpdated 更新后回调 可能循环更新,低频用
useXxx 逻辑复用打包 返回普通值响应性死亡

边界与延伸

本字典只收基础层:Pinia、Router、SSR(Nuxt)各有专门文档与生态,不在这里铺开;watch 的 flush 时机、effectScope 等进阶话题同样以官方 API 索引为准。React 背景的读者,watch/watchEffect 与 useEffect 的执行模型差异值得对照《React 渲染与重渲染》想一遍——什么时候跑、跑几遍,两套框架答案不同。

版本核实(撰写时检索,2025-03):Vue 当前稳定线为 3.5.x;defineModel 自 3.4 稳定;v-model 参数、作用域插槽均为 Vue 3 稳定特性;3.6(Vapor Mode)仍在实验、未发布,本字典未涉及。具体以官方文档为准。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇