Vue3 语法摘要
本文将 Vue3 中的组合式语法精简为快速笔记,方便在使用中总览 Vue 特性,灵活应用。
该文不会介绍具体用法,建议先通读一遍 官方帮助文档,记得风格偏好中选择自己喜欢的网络
如果某些概念读不懂,可以先去看官方文档
简写扫盲
简写 | 全称 | 中文 |
---|---|---|
SFC | Single File Component | 单文件组件 |
E2E | End to End | 端到端的测试 |
开发工具建议
类别 | 技术名称 |
---|---|
脚手架 | Vite |
IED | VSCode + Vue 语言特性 (Volar) |
TypeScript | |
Vite 项目测试 | Vitest |
E2E 测试 | Cypress |
代码规范 | eslint-plugin-vue |
字符串内联模板语法高亮 | es6-string-html |
基础
模板
模板语法
可以不采用模块,而是 直接手写渲染函数。
类别 | 概要 |
---|---|
文本插值 | 采用“Mustache”语法 (即双大括号):Message: |
使用原始 HTML | v-html
指令: |
Attribute 绑定 | v-bind
指令:v-bind:id= 或 :id= |
布尔型 Attribute 绑定 | 不赋值时,为真
<button disabled>Button</button> |
多个值动态绑定 | <div v-bind="objectOfAttrs"></div> |
表达式 | 用在 双大括号 中或 Vue
指令中,可以使用有限的全局对象,例Math 和
Date |
指令参数 | 在指定后添加 : 作为参数, |
指令动态参数 | <a v-bind:[attributeName]="url" />,attributeName 可以是表达式或计算属性 |
ref 模板引用
但在某些情况下,我们使用元素的 ref 属性来直接访问底层 DOM 元素。
1 | <input ref="input"> |
声明一个同名的 ref 来访问 ref 指定的 DOM
1 | <script setup> |
如果不使用 <script setup>
,需确保从
setup()
返回 ref:
1 | export default { |
函数模板引用
1 | <input :ref="(el) => { /* 将 el 赋值给一个数据属性或 ref 变量 */ }"> |
每次组件更新时,绑定的元素被卸载时都会调用函数
组件 ref 限制
使用了 <script setup>
的组件是默认私有的,可以通过 defineExpose
宏显式向外暴露访问内容。
1 | <script setup> |
指令
指令是带有 v-
前缀的特殊 attribute。
指令定义
参数 Arguments
某些指令会需要一个“参数”,在指令名后通过一个冒号隔开做标识。
动态参数
在指令参数上也可以使用一个 JavaScript 表达式。
限制:
动态参数中表达式的值应当是一个字符串,或者是
null
。特殊值 null
意为显式移除该绑定。
动态参数表达式不能正在空格和引号,它们在 HTML attribute 名称中都是不合法的。可以使用计算属性来代替表达式。
内置指令
名称 | 缩写 | 期望值 | 描述 |
---|---|---|---|
v-text | string | 等同于 {{content}} 语法 |
|
v-html | string | 更新元素的 innerHTML,scoped
样式将不会作用于 v-html 里的内容 |
|
v-show | any | 通过设置内联样式的 display CSS 属性来工作 |
|
v-if | any | 条件性地渲染,触发时元素及其所包含的指令/组件都会销毁和重构 | |
v-else/v-else-if | |||
v-for | Array | Object | number | string | Iterable | <div v-for="(item, index) in items"></div> <div v-for="(value, key) in object"></div> <div v-for="(value, name, index) in object"></div> |
|
v-on | @ | Function | Inline Statement | Object (不带参数) | 给元素绑定事件监听器。 |
v-bind | : | any (带参数) | Object (不带参数) | 动态的绑定一个或多个 attribute,也可以是组件的 prop |
v-model | 在表单输入元素或组件上创建双向绑定 | ||
v-slot | # | 用于声明具名插槽或是期望接收 props 的作用域插槽 | |
v-pre | 跳过该元素及其所有子元素的编译。最常见的用例就是显示原始双大括号标签及内容。 | ||
v-once | 仅渲染元素和组件一次,并跳过之后的更新。 | ||
v-memo | 缓存一个模板的子树。 | ||
v-cloak | 用于隐藏尚未完成编译的 DOM 模板。 |
响应式基础
用 reactive()
定义响应式变量
使用 reactive()
函数创建一个响应式对象或数组:
1 | import { reactive } from 'vue' |
响应式对象是 JavaScript Proxy
reactive()
创建的是深层响应,可以使用 shallowreactive
来创建仅第一级响应。
reactive()
缺点
reactive()
API 有两条限制:
- 仅对对象类型有效(对象、数组和
Map
、Set
这样的集合类型),而对string
、number
和boolean
这样的 原始类型 无效。 - 因为 Vue 的响应式系统是通过属性访问进行追踪的,使用时必须使用
响应式对象.属性
的方式访问和赋值,如果赋值给其它变量,则不会传递响应式效果
响应式代理
开发中建议仅使用声明对象的代理版本。
对同一个原始对象调用 reactive()
会总是返回同样的代理对象,而对一个已存在的代理对象调用
reactive()
会返回其本身。
响应式对象内的嵌套对象依然是代理。
用 ref()
定义响应式变量
1 | import { ref } from 'vue' |
ref()
方法来允许创建任何值类型的响应式 ref。
ref()
将传入参数的值包装为一个带 .value
属性的 ref 对象,通过 .value
来访问。
当值为对象类型时,会用 reactive()
自动转换它的
.value
。
当 ref
在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用
.value
。
当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包。
当 ref 作为响应式数组或像 Map
这种原生集合类型的元素被访问时,不会进行解包。
暴露响应式状态
要在组件模板中使用响应式状态,需要在 setup()
函数中定义并返回。
1 | import { reactive } from 'vue' |
<script setup>
使用 <script setup>
来简化手动暴露状态和方法。
1 | <script setup> |
顶层的导入和变量声明可在同一组件的模板中直接使用,相当于自动暴露给模板了。
DOM 更新时机
无论你进行了多少次状态更改,每个组件都只更新一次。
若要等待一个状态改变后的 DOM 更新完成,你可以使用 nextTick() 这个全局 API。
计算属性
1 | import { computed } from 'vue' |
返回值会自动打包成一个 ref
可以通过 get()
和 set(newValue)
控制计算属性的读写
class 绑定
class
与 style
在使用 v-bind
时,除字符串外,表达式的值也可以是对象或数组。
对象和数组可以是内联的,也可以是独立的,还可以是 computed 类型
绑定对象
1 | const isActive = ref(true) |
绑定数组
字符串数组形式:
1 | const activeClassName = ref('active') |
三元表达式形式:
1 | <div :class="[isActive ? activeClass : '', errorClass]"></div> |
对象数组形式:
1 | <div :class="[{ active: isActive }, errorClass]"></div> |
组件的 class 规则
只有一个根元素的组件,组件上绑定的 class 自动与根元素合并
有多个根元素时,通过 $attrs
来指定绑定
1 | <!-- MyComponent 模板使用 $attrs 时 --> |
style 绑定
绑定对象
1 | <div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div> |
绑定数组
绑定一个包含多个样式对象的数组,对象会被合并。
1 | <div :style="[baseStyles, overridingStyles]"></div> |
自动前缀
当在 :style
中使用了需要浏览器特殊前缀的
CSS 属性时,Vue 会自动为他们加上相应的前缀
样式多值
数组仅会渲染浏览器支持的最后一个值
1 | <div :style="{ display: ['-webkit-box', '-ms-flexbox', 'flex'] }"></div> |
v-if 条件渲染
v-if
、v-else-if
、v-else
、v-show
v-if
有更高的切换开销,而 v-show
有更高的初始渲染开销。
v-if
的优先级高于 v-for
,v-if
的条件将无法访问到 v-for
作用域内定义的变量别名
同时使用 v-if
和 v-for
是不推荐的,可以通过包装一层 template 来解决
1 | <template v-for="todo in todos"> |
v-for 列表渲染
1 | <li v-for="(value, key, index) in myObject"> |
v-for
可以直接接受一个整数值。
1 | <span v-for="n in 10">{{ n }}</span> |
可以在任何时候为 v-for
提供一个 key,提高渲染效率。
数组更新、替换后,不会重新渲染整个列表,而是最大化重用。
事件处理
使用 v-on
指令 (简写为 @
) 来监听 DOM
事件,并在事件触发时执行对应的
JavaScript。用法:v-on:click="methodName"
或
@click="handler"
。
$event
变量代表原生事件,可以通过内联事件传递给事件处理函数。
1 | @click="handler(message,$event)" |
事件修饰符
1 | <!-- 修饰语可以使用链式书写 --> |
序号 | 修饰符 | 作用 |
---|---|---|
1 | .stop |
单击事件将停止传递 |
2 | .prevent |
如果此事件没有被显式处理,阻止事件的默认行为。 比如 submit 的提交后会刷新界面的行为就该事件的默认行为 |
3 | .self |
仅当 event.target 是元素本身时才会触发事件处理器 |
4 | .capture |
在事件在捕获阶段到达该元素时触发 |
5 | .once |
仅执行一次 |
6 | .passive |
事件的默认行为立即执行 |
7 | .{keyAlias} |
只在某些按键下触发处理函数。 |
使用修饰符时需要注意调用顺序,因为相关代码是以相同的顺序生成的。
因此使用
@click.prevent.self
会阻止元素及其子元素的所有点击事件的默认行为,而
@click.self.prevent
则只会阻止对元素本身的点击事件的默认行为。
按键修饰符
用于监听键盘事件。使用 KeyboardEvent.key
暴露的按键名称作为修饰符,但需要转为 kebab-case 形式。
1 | <input @keyup.page-down="onPageDown" /> |
按键别名:
.enter
、.tab
、.delete
(捕获“Delete”和“Backspace”两个按键)、.esc
、
.space
、.up
、 .down
、
.left
、 .right
系统按键:
.ctrl
、 .alt
、 .shift
、
.meta
鼠标按键修饰符:
.left
、.right
、.middle
.exact
修饰符:
仅响应确定组合的事件
1 | <!-- 当按下 Ctrl 时,即使同时按下 Alt 或 Shift 也会触发 --> |
v-model 输入绑定
v-model 将 value 绑定到元素上,并监听 value 的变化,达到数据绑定的效果。
- 文本类型的
<input>
和<textarea>
元素会绑定value
property 并侦听input
事件; <input type="checkbox">
和<input type="radio">
会绑定checked
property 并侦听change
事件;<select>
会绑定value
property 并侦听change
事件。
v-model
会忽略任何表单元素上初始的
value
、checked
或 selected
attribute。
修饰符
1 | <!-- 在 "change" 事件后同步更新而不是 "input" --> |
修饰符 | 作用 |
---|---|
.lazy |
将数据修改从 input 事件修改到 change 事件后触发 |
.number |
用户输入自动转换为数字 |
.trim |
自动去除用户输入内容中两端的空格 |
生命周期
监听器
watch
watch
的第一个参数可以是不同形式的“数据源”:它可以是一个
ref (包括计算属性)、一个响应式对象、一个 getter
函数、或多个数据源组成的数组:
1 | const x = ref(0) |
不能直接监听响应式对象的属性值,必须使用像上例中的 getter 函数形式。
watch 默认是深层监听器,如果只想监听某个属性,可以使用 getter 函数方式
watchEffect
watchEffect
的回调会立即执行一次,它会自动追踪依赖,有点类似
computed
watchEffect
仅会在其同步执行期间,才追踪依赖。在使用异步回调时,只有在第一个await
正常工作前访问到的属性才会被追踪。
回调触发
默认情况下,用户创建的侦听器回调,都会在 Vue 组件更新之前被调用。在侦听器回调中访问的 DOM 是被 Vue 更新之前的状态。
使用 flush: 'post'
来设置监听回调在 DOM 更新后触发。
1 | watch(source, callback, { |
watchPostEffect
等效于:
1 | watchEffect(callback, { |
停止侦听器
同步语句创建的侦听器,会自动停止,但是在异步中调用创建的监听器需要调用其返回值手动停止
1 | <script setup> |
组件
组件基础
组件定义
script setup 形式:
1 | <script setup> |
非 setup 形式:
1 | import { ref } from 'vue' |
组件使用
1 | <script setup> |
传递 props
script setup 使用 defineProps 定义:
1 | <script setup> |
非 setup,使用 props
定义,在
setup(props,ctx)
中读取值
触发事件
通过 defineEmits
宏来声明需要抛出的事件。
在 script setup 中:
1 | const emit = defineEmits(['eventName']) |
在 setup() 中:
1 | export default { |
插槽
使用 <slot />
来点位
动态组件
使用 is
属性实现
1 | <!-- currentTab 改变时组件也改变 --> |
被传给 :is
的值可以是以下几种:
- 被注册的组件名
- 导入的组件对象
当使用 <component :is="...">
来在多个组件间作切换时,被切换掉的组件会被卸载。
通过 KeepAlive强制被切换掉的组件仍然保持“存活”的状态。
大小写
HTML 标签和属性名称是不分大小写的,所以浏览器会把任何大写的字符解释为小写。
Vue 对组件元素做了预处理,因此在使用时,建议组件使用 PascalCase 命名方式。
组件可以使用 </>
作为关闭标签。
注册组件
全局注册
使用 Vue 实例的 app.component()
方法,注册全局组件
1 | import { createApp } from 'vue' |
单文件组件注册成全局组件:
1 | import MyComponent from './App.vue' |
app.component()
方法可以被链式调用。
局部注册
在使用 <script setup>
的单文件组件中,导入的组件可以直接在模板中使用,无需注册。
如果没有使用 <script setup>
,则需要使用
components
选项来显式注册。
props
props 定义
在使用 <script setup>
的单文件组件中,props
可以使用 defineProps()
宏来声明:
1 | <script setup> |
在没有使用 <script setup>
的组件中,prop 可以使用
props
选项来声明:
1 | export default { |
defineProps 与 props 中传递的参数是一样,它们有以下几种形式:
字符串数组,
[propName1,propName2,...]
,每个字符串表示特性名称对象
1
2
3
4
5// 使用 <script setup>
defineProps({
title: String,
likes: Number
})
<script setup>
还可以使用类型标注来声明:
1 | <script setup lang="ts"> |
传递值给 props
1 | <!-- 传入静态值 --> |
使用一个对象绑定多个 prop
可以将一个对象的所有属性都当作 props 传入
1 | const post = { |
单向绑定
props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。
不应该在子组件中去更改一个 prop。
导致想要更改一个 prop 的需求通常来源于以下两种场景:
prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可:
1
2
3
4
5const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值
// 像下面这样做就使 prop 和后续更新无关了
const counter = ref(props.initialCounter)需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性:
1
2
3
4const props = defineProps(['size'])
// 该 prop 变更时计算属性也会自动更新
const normalizedSize = computed(() => props.size.trim().toLowerCase())
props 校验
校验形式如下:
1 | defineProps({ |
校验中的 type 有:
String
Number
Boolean
Array
Object
Date
Function
Symbol
- 也可以是自定义类型,vue 通过 instanceof 来匹配
事件
声明
$emit
只能在模板中使用。
在 script setup
中无法使用,可以通过
defineEmits
来获取 emit
1 | const emit = defineEmits(['inFocus', 'submit']) |
defineEmits()
宏必须直接放置在
<script setup>
的顶级作用域下。
非 <script setup>
中,事件需要通过 emits
选项来定义,emit
函数也被暴露在 setup()
的上下文对象上:
1 | export default { |
事件定义支持对象语法,它允许对触发事件的参数进行验证:
1 | <script setup> |
也可以使用 TypeScript 的类型标注来验证:
1 | <script setup lang="ts"> |
触发
事件通过 v-on
(简写为 @
) 来进行监听。
在组件的模板表达式中,也可以直接使用 $emit
方法触发自定义事件。
1 | <button @click="$emit('someEvent')">click me</button> |
组件触发的事件没有冒泡机制
参数
第一个参数为事件名,第二个参数为事件参数
emit(emitName,eventArgs)
校验
在对象定义中,通过添加 submit
来进行验证
1 | <script setup> |
组件 v-model
v-model
本质
v-model
通过属性绑定和事件监听实现的。
1 | <input v-model="searchText" /> |
v-model 实现方式
- 定义一个 props,名为
modelValue
- 当 props 变化时,触发
update:modelValue
事件
事件传递的参数即为 v-model 的新值
1 | <CustomInput |
还可以使用 computed 属性来实现
1 | <!-- CustomInput.vue --> |
多个 v-model
默认情况下,v-model
在组件上都是使用
modelValue
作为 prop,并以 update:modelValue
作为对应的事件。
可以通过给 v-model
指定一个参数来更改
v-model
映射。
1 | <UserName |
自定义修饰符
自定义的修饰符通过 props.modelModifiers
可以访问到。在触发事件时,可以判断 Modifiers
中是否有修饰符,如果有,根据修饰符对数据进行处理后再触发事件。
1 | <script setup> |
又有参数又有修饰符的 v-model
绑定,生成的 prop 名将是
arg + "Modifiers"
1 | <MyComponent v-model:title.capitalize="myText"> |
Attributes 继承
父组件向子组件传递 Attributes 时,若子组件没有在 props 和 emits 中声明,则会向下继承。
当一个组件以单个元素为根作渲染时,继承的 attribute 会自动被添加到根元素上。
继承的 class
和 style
会与子组件上的相同属性合并。
访问继承
在 <script setup>
中使用 useAttrs()
API 来访问一个组件继承的所有 attribute
1 | <script setup> |
在 setup(props,ctx)
函数中通过 ctx.attrs
来访问
attrs
对象总是反映为最新的
attribute,但它并不是响应式的。
禁用继承
设置 inheritAttrs: false
来禁用自动继承。
1 | <script> |
继承的 Attributes 可以在模板中直接用 $attrs
访问到。
这个 $attrs
对象包含了除组件所声明的 props
和 emits
之外的所有其他 attribute,例如
class
,style
,v-on
监听器等等。
有几点需要注意:
- 和 props 有所不同,继承的 attributes 在 JavaScript
中保留了它们原始的大小写,所以像
foo-bar
这样的一个 attribute 需要通过$attrs['foo-bar']
来访问。 - 像
@click
这样的一个v-on
事件监听器将在此对象下被暴露为一个函数$attrs.onClick
。
指定继承
可以在子组件中通过 v-bind 来将 attributes 绑定到其它组件上
1 | <div class="btn-wrapper"> |
多根节点的 Attributes 继承
需要手动指定继承,无法像单根节点一样,自动继承
插槽 Slots
<slot>
元素是一个插槽出口 (slot
outlet),标示了父元素提供的插槽内容 (slot content)
将在哪里被渲染。
渲染作用域
插槽内容可以访问到父组件的数据作用域,无法访问子组件的数据。
默认内容
放置 <slot>
标签时,标签之间的内容作为默认内容。
具名插槽
带 name
属性的插槽叫具名插槽。没有提供 name
的 <slot>
出口会隐式地命名为“default”。
使用 v-slot:slotName
(简写为 #slotName
)
方式来指定所使用的插槽。
默认插槽不用指定。
当一个组件同时接收默认插槽和具名插槽时,所有位于顶级的非
<template>
节点都被隐式地视为默认插槽的内容。如下:
1 | <BaseLayout> |
动态插槽
通过指令的动态参数传递不同的名称实现动态插槽。
插槽传递参数
插槽的内容无法访问到子组件的状态,在定义插槽时,我们需要先将组件数组绑定到插槽上,使用时,再从插槽中获取。
数据绑定:
1 | <!-- <MyComponent> 的模板 --> |
参数接收:
- 默认插槽使用
v-slot="slotProps"
或者#default="slotProps"
来接收参数 - 具名插槽使用
#slotName="slotProps"
来接收参数
无渲染组件
一些组件可能只包括了逻辑而不需要自己渲染内容,视图输出通过作用域插槽全权交给了消费者组件。我们将这种类型的组件称为无渲染组件。
依赖注入
依赖注入用于多级父子组件传值,解决 props 传值链路长的问题。
props 传值:
依赖注入:
Provide (提供)
1 | <script setup> |
第一个参数被称为注入名,可以是一个字符串或是一个
Symbol
第二个参数是提供的值,值可以是任意类型,包括响应式的状态
可以在应用层进行依赖注入:
1 | import { createApp } from 'vue' |
Inject (注入)
使用 inject()
函数注入上层组件提供的数据。
1 | import { inject } from 'vue' |
使用建议
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
1 | <!-- 在供给方组件内 --> |
1 | <!-- 在注入方组件 --> |
只读数据
可以使用 readonly()
来包装提供的值使其不能被修改
异步组件
定义
使用 defineAsyncComponent
方法来实现按需从服务器加载相关组件。
1 | import { defineAsyncComponent } from 'vue' |
ES
模块动态导入也会返回一个 Promise,所以多数情况下我们会将它和
defineAsyncComponent
搭配使用。类似 Vite 和 Webpack
这样的构建工具也支持此语法
(并且会将它们作为打包时的代码分割点),因此我们也可以用它来导入 Vue
单文件组件:
1 | import { defineAsyncComponent } from 'vue' |
加载与错误状态
1 | const AsyncComp = defineAsyncComponent({ |
逻辑复用
组合式函数
“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。在开发中可以将功能细分成一个个组合式函数,最后组合成功能。
约定和最佳实践
命名
组合式函数约定用驼峰命名法命名,并以“use”作为开头。
输入参数兼容 ref
使用 unref
来兼容输入参数
1 | import { unref } from 'vue' |
返回值
组合式函数应始终返回一个包含多个 ref 的普通的非响应式对象,这样该对象在组件中被解构为 ref 之后仍可以保持响应性。
从组合式函数返回一个响应式对象会导致在对象解构过程中丢失与组合式函数内状态的响应性连接。
如果更希望以对象属性的形式来使用组合式函数中返回的状态,可以将返回的对象用
reactive()
包装一次,这样其中的 ref
会被自动解包,例如:
1 | const mouse = reactive(useMouse()) |
额外操作
如果你的应用用到了服务端渲染
(SSR),请确保在组件挂载后(onMounted
)才调用的生命周期钩子中执行
DOM 相关的其它操作,并在 onUnmounted()
中释放资源。
使用限制
组合式函数在 <script setup>
或
setup()
钩子中,应始终被同步地调用。
通过抽取组合式函数改善代码结构
可以基于逻辑问题将组件代码拆分成更小的函数
1 | <script setup> |
在选项式 API 中使用组合式函数
如果你正在使用选项式 API,组合式函数必须在 setup()
中调用。且其返回的绑定必须在 setup()
中返回,以便暴露给
this
及其模板
自定义指令
定义与使用
在 <script setup>
中,任何以 v
开头的驼峰式命名的变量都可以被用作一个自定义指令。例如vFocus
可以在模板中以 v-focus
的形式使用。
在没有使用 <script setup>
的情况下,自定义指令需要通过 directives
选项注册。
1 | export default { |
指令钩子
一个指令的定义对象可以提供几种钩子函数 (都是可选的):
1 | const myDirective = { |
简化形式
1 | app.directive('color', (el, binding) => { |
在组件上使用
当在组件上使用自定义指令时,它会始终应用于组件的根节点。
指令不能通过 v-bind="$attrs"
来传递给一个不同的元素。
插件
插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:
- 通过
app.component()
和app.directive()
注册一到多个全局组件或自定义指令。 - 通过
app.provide()
使一个资源可被注入进整个应用。 - 向
app.config.globalProperties
中添加一些全局实例属性或方法 - 一个可能上述三种都包含了的功能库 (例如 vue-router)。
1 | // 定义 |
内置组件
Transition
Vue 提供了两个内置组件来制作基于状态变化的过渡和动画:
<Transition>
会在一个元素或组件进入和离开 DOM 时应用动画。<TransitionGroup>
会在一个v-for
列表中的元素或组件被插入,移动,或移除时应用动画。