目录
一、 插值表达式
1、插值表达式 ({{}}) 的本质与作用:
2、与 Vue 响应式系统关系:
二、指令
1、什么是 Vue 指令?
2、指令的分类
1、内置指令
① 内容绑定:v-text 和 v-html
② 属性绑定:v-bind
③ 事件绑定:v-on
④ 条件渲染:v-if、v-else-if、v-else
⑤ 列表渲染:v-for
⑥ 双向绑定:v-model
⑦ 性能优化:v-once、v-memo
⑧ 其他指令
v-pre:跳过模板编译阶段(提升渲染性能)。
v-cloak:防止插值表达式闪烁(未编译完毕时隐藏元素)。
2、自定义指令(用户扩展)
3、javascript表达式
4、动态参数
1、什么是「动态参数」?
2、动态参数值的限制:
3、参数语法的限制:
三、响应式基础
1、ref()
1、 在模板中如何使用?
2、模板 ref(获取DOM元素引用)
3、 为 ref() 标注类型
1、为什么要为 ref() 标注类型?
2、如何为 ref() 标注类型?
方法一:显式标注类型(推荐)
方法二:类型推导(隐式标注)
3、复杂类型如何标注类型?
4、模板 ref 的类型标注
2、reactive()
1、reactive() 基本用法
2、在模板中使用:
3、reactive() 深层响应式特性
4、ref 解包
为什么会存在额外的 ref 解包?
需要注意的问题(⚠️重要)
问题1:在赋值新值时仍保持 ref 特性
问题2:解构赋值问题
数组和集合的注意事项
在模板中解包的注意事项
5、为 reactive 标注类型
方式一:使用接口(Interface)标注类型(推荐)
方式二:使用类型别名(type)标注类型
方式三:显式类型断言(不推荐,偶尔使用)
为复杂嵌套数据标注类型的实践示例
使用泛型标注 reactive 的常见错误
我们知道vue的特点是
声明式渲染:Vue 基于标准 HTML 拓展了一套模板语法,使得我们可以声明式地描述最终输出的 HTML 和 JavaScript 状态之间的关系。
响应性:Vue 会自动跟踪 JavaScript 状态并在其发生变化时响应式地更新 DOM。
最显著的一个特点就是响应式,那么引出插值表达式
{{}}
) 的本质与作用:插值表达式是 Vue 模板语法的一部分,它告诉 Vue:
这里需要绑定数据,请自动更新这个区域的内容。
{{ message }}
插值用于显示在元素内部的文本内容。
本质上是在告诉 Vue:
“请帮我监听响应式数据对象中的 message
字段。”
“当它的值发生变化时,请自动更新这个 作用: 数据绑定(Data Binding) 绑定响应式数据的值,使得视图总能与数据保持同步。 自动更新 UI 数据变化后,Vue 会自动地更新插值表达式对应的 DOM 区域。 便捷显示数据 简单直观地展示动态内容,减少繁琐的 DOM 操作。 除了基本变量名,插值表达式内还可以使用简单的 JavaScript 表达式: 插值表达式依靠 Vue 响应式系统: Vue 内部通过 当响应式数据变化,Vue 自动触发页面重新渲染,更新插值内容。 有小伙伴回想Vue 已经响应式了,为何还用插值? Vue 是响应式框架,响应式的含义是数据变化后自动更新视图,而插值表达式 ( 插值表达式让数据的自动变化直接体现在页面显示上,开发者无需手动编写监听与更新代码。 我们知道了插值表达式以后要注意两点 1、插值表达式无法用在 HTML 属性中 因此我们等会要学习指令,指令使得我们的元素的属也可以变成响应式 2、 插值表达式仅适合简单逻辑、 插值中不要编写过于复杂的表达式。 复杂逻辑建议抽离到 在 Vue 中,指令(Directives) 是带有前缀 Vue 的指令本质上是: 一种模板语法扩展。 用来在模板中声明式地控制 DOM 的行为、渲染逻辑以及响应式交互。 Vue 指令分为: 内置指令(Vue 自带):如 自定义指令(用户定义):可根据需求自定义,如: 插值表达式会将数据解释为纯文本,而不是 HTML。若想插入 HTML,你需要使用v-html指令 假如你的数据是这样的: 使用插值表达式 ( 页面上会直接显示:这是红色字体! (HTML标签会显示成原样,而不会解析为真正的HTML元素) 使用 页面实际渲染结果: 这次页面会真正地把字符串解析为HTML元素,显示为: 这是红色字体!(字体颜色为红色) 使用 Vue 使用 如果插入了用户提供的不可信内容(如用户留言、评论等),可能引发 XSS(跨站脚本攻击)风险。 作用:动态绑定元素的属性或组件的 props。 常用场景:动态class绑定、style绑定、组件传值。 作用:为元素绑定事件监听器,如 click、submit 等。 事件修饰符(常用): 根据条件渲染元素: 注意: 补充: 适用于频繁切换场景。 循环遍历数组或对象:必须指定 将表单控件与数据自动双向绑定,数据和视图实时同步: 常用修饰符: {{ raw }} 当内置指令不能满足需求时,用户可以创建自定义指令。 注册示例(Vue 3.x): 使用自定义指令: 指令的生命周期钩子(自定义指令专用) 在 Vue 中,模板语法本质上是基于 JavaScript 表达式的。 也就是说: 在 Vue 模板的插值表达式 ( Vue 在渲染模板时会自动对这些 JavaScript 表达式进行求值,并将结果渲染到视图上。 JavaScript表达式 (Expression) 是一种产生值的代码片段。它的特点是: 任何能返回一个值的语句都是表达式。 表达式求值后一定会得到一个具体的值。 表达式本身可以由: 字面量(Literal) 如数字、字符串。 变量引用(如: 运算符组合(如: 函数调用(如: 三元运算符(如: 在 Vue 模板中只能使用表达式,不能使用语句。 传统绑定是静态的(固定属性名):链接 此时: 它的实际值由 动态参数表达式的值,必须是字符串或 动态参数内的表达式只允许简单表达式,不允许复杂的 JavaScript 语句。 比如不能直接写三元表达式、函数调用、对象属性访问等复杂逻辑。 示例:假设有个按钮组件,动态绑定事件 动态决定绑定的事件类型。 极大增加组件的可复用性和灵活性。 特点: 可用来处理任何类型(基本类型、对象、数组)。 通常更常用于基本类型数据(例如数字、字符串、布尔值)。 访问数据时需通过 访问数据时:console.log(count.value) // 输出 0 修改数据:count.value++ ⚠️ 注意: 在模板中,Vue 自动帮你解包 模板中:直接使用 JavaScript 中:需要 ref 如何处理对象类型? 虽然推荐使用 但注意: 使用 若直接替换对象时必须用 ⚠️ 若想避免频繁使用 const obj = reactive({ name: 'Tom', age: 20 }) 在模板里使用 通常用于直接操作DOM或子组件(如:聚焦、调用子组件方法)。 默认情况下,Vue 3 已经提供了对 TypeScript 的完整支持,但在使用 提升代码可读性: 其他开发者一看就知道你的数据类型是什么。 获得更好的类型检查: IDE 能精准识别类型错误,避免潜在Bug。 IDE 更好的自动补全支持: 更流畅的开发体验,提高效率。 使用泛型明确指定类型: 如果你没有显式指定泛型类型,Vue 会根据初始值自动推导类型: 但注意: 如果初始值是 const name = ref(null) // 推断为 Ref 这种情况建议你显式标注类型: 常用接口或类型别名定义复杂对象,再传给 接口类型示例: 类型别名示例: 模板 ref 是指用于绑定到 DOM 元素或组件引用的 需要标注为 4、与Ref的关系 它用于明确地描述由 其定义为: 使用示例: 两者之间的关系是实现与类型声明的关系: 在 Vue 3 中: 当对象或数组的数据发生变化时,Vue 会自动更新使用该数据的视图。 本质上: 所有对该对象属性的读取、修改操作都会被拦截,从而实现响应式更新。 使用响应式数据: 在模板中, 注意: 和 reactive vs ref 区别对比 在 Vue 3 中: 使用 深层响应式意味着: 如果一个对象中嵌套了其他对象或数组,这些嵌套的对象或数组也会自动成为响应式。 不论嵌套对象有多少层,修改任何一层中的属性,都会自动触发视图的更新。 简单理解为: reactive() 会递归地将整个对象树都变为响应式。 假设我们创建一个嵌套的对象: 这时, 不需要额外手动声明嵌套数据为响应式。 Vue 会自动递归 Proxy 化整个嵌套结构。 深层响应式的内部原理(Proxy递归机制) 深层响应式的实现,背后的机制是 JavaScript 的 Proxy 对象: 简化版原理: 当访问对象属性( 这样确保所有嵌套对象都实现响应式。 深层响应式 vs 浅层响应式 为了应对某些特定场景,Vue 也提供了浅层响应式(Shallow Reactive): 正确方式(toRefs): 使用 Vue 提供的 在 Vue 3 的响应式系统中,存在一种特别的机制称为 Ref 解包(Ref Unwrapping): 当你使用 在模板中使用这些数据时,Vue 会自动帮你去掉 然而,除了模板中的自动解包外,Vue 还在一些特定场景中提供了 额外的 ref 解包机制: 即在一些特殊的响应式对象中(例如用 看一个直观的例子: 额外的 ref 解包主要是为了: 让开发者在使用嵌套响应式数据时更方便、直观,不必在模板外过多考虑 特别是在构建复杂的响应式数据结构时,使代码更加简洁、直观。 如果没有这个机制: 使用 reactive 对象嵌套 ref 时,每次访问内部值都必须手动使用 适用场景 如果你直接对 reactive 对象解构,则会失去响应性: 与 reactive 对象不同的是,当 ref 作为响应式数组或原生集合类型 (如 在模板渲染上下文中,只有顶级的 ref 属性才会被解包。 在下面的例子中, 为 接口是一种定义复杂对象结构的最佳方式: 类型别名用法与接口类似,适合定义简单或复合类型: 显式类型断言能快速定义类型,但会牺牲类型检查严谨性,一般不推荐: ⚠️ 注意: 类型断言会略过类型校验,可能隐藏错误。 示例: 明确的类型标注帮助 IDE 提供更精确的属性提示。 有些开发者可能尝试这样做(但这是错误的): 正确的写法是用泛型指定:
2、与 Vue 响应式系统关系:
ref
或 reactive
等 API 使数据变得响应式。
{{}}
) 就是这种自动更新机制体现到模板上的一种方式。
computed
或方法中二、指令
1、什么是 Vue 指令?
v-
的特殊属性,用于在模板中提供特定的行为或功能。
2、指令的分类
v-if
、v-for
等。v-focus
。 1、内置指令
① 内容绑定:
v-text
和 v-html
v-text
:更新元素的文本内容,与插值表达式作用类似,但会覆盖元素内所有内容。
v-html
:输出原始 HTML 内容(会解析HTML标签)。 data() {
return {
rawHtml: '这是红色字体!'
}
}
{{ }}
):
v-html
指令:v-html
时必须特别注意安全问题:
v-html
时会解析 HTML 字符串,但不会对内容进行安全过滤。 ② 属性绑定:
v-bind
链接
链接
③ 事件绑定:
v-on
修饰符
作用
.stop
阻止事件冒泡
.prevent
阻止默认事件
.once
只触发一次
.capture
事件捕获模式
④ 条件渲染:
v-if
、v-else-if
、v-else
v-if
会完全删除或重建 DOM 节点。v-show
v-show
仅控制元素的 display
属性,不删除 DOM。⑤ 列表渲染:
v-for
key
属性来优化更新效率。
⑥ 双向绑定:
v-model
修饰符
作用
.lazy
在 input 失去焦点或回车时才同步
.number
将输入转为数值类型
.trim
自动过滤首尾空白字符
⑦ 性能优化:
v-once
、v-memo
v-once
:只渲染一次,不再响应后续数据变化:{{ staticContent }}v-memo
(Vue 3.2+):精确控制渲染,只有依赖的值变化时才重新渲染: ⑧ 其他指令
v-pre
:跳过模板编译阶段(提升渲染性能)。
v-cloak
:防止插值表达式闪烁(未编译完毕时隐藏元素)。
2、自定义指令(用户扩展)
const app = Vue.createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
钩子函数
描述
created
指令绑定元素后调用
beforeMount
元素插入到 DOM 前调用
mounted
元素插入到 DOM 后调用
beforeUpdate
更新前调用
updated
更新后调用
beforeUnmount
元素卸载前调用
unmounted
元素卸载后调用
3、javascript表达式
{{}}
) 或指令(如 v-bind
、v-if
、v-for
)中,实际填写的内容本质上都是 JavaScript 表达式。
x
)。a + b
)。getName()
)。x > 1 ? 'Yes' : 'No'
)等组成。// 字面量
42; // 数字表达式
"Hello"; // 字符串表达式
// 算术表达式
1 + 2; // 求值为 3
// 逻辑表达式
a > b && c < d; // 返回 true 或 false
// 三元表达式
isActive ? '是' : '否';
// 函数调用表达式
getUsername(); // 返回调用函数的返回值
4、动态参数
1、什么是「动态参数」?
动态参数的作用是,允许动态决定绑定哪个属性: 链接
[attributeName]
是一个动态参数。attributeName
这个数据属性的值决定。2、动态参数值的限制:
null
,其他类型的值(如数字、对象、布尔值)会报错。3、参数语法的限制:
三、响应式基础
1、ref()
ref()
是 Vue 3 中一个重要的响应式 API,专门用来创建响应式数据。它的全称为 reference(引用),作用是将一个基本类型数据或对象包裹成响应式对象,使数据能够在发生变化时自动通知视图更新。
.value
属性。import { ref } from 'vue'
const count = ref(0) // 创建一个响应式数据,初始值为 0
console.log(count.value) // 输出 1
ref()
返回的是一个对象,所以必须使用 .value
来访问真正的值。1、 在模板中如何使用?
.value
:
count
即可。count.value
。ref()
来处理基本类型数据,但也可以用它创建对象或数组:const obj = ref({ name: 'Tom', age: 20 })
console.log(obj.value.name) // Tom
obj.value.age = 21 // 依然是响应式更新
ref
创建的对象类型数据本质上是在 ref 中存储一个 reactive 对象。.value
:// 正确方式:
obj.value = { name: 'Jerry', age: 22 }
.value
,可用 reactive
替代: 2、模板 ref(获取DOM元素引用)
ref
属性获取 DOM 元素或子组件的引用:
inputRef
会绑定到对应的DOM或子组件实例上。3、 为
ref()
标注类型1、为什么要为
ref()
标注类型?ref()
创建响应式数据时,明确地标注类型会带来更多的好处:
2、如何为
ref()
标注类型? 方法一:显式标注类型(推荐)
import { ref } from 'vue'
// 标注为数字类型的ref
const count = ref
方法二:类型推导(隐式标注)
import { ref } from 'vue'
// 自动推断 count 为 number 类型的 Ref
null
或 undefined
,Vue 会推导成宽泛类型 Ref
或 Ref
,这通常不是我们想要的:// 更好的写法:
const name = ref
3、复杂类型如何标注类型?
ref()
:interface Article {
title: string
content: string
likes: number
}
// 显式指定 Article 类型
const article = ref
type Status = 'success' | 'error' | 'loading'
const status = ref
4、模板 ref 的类型标注
ref
:
inputRef
必须初始化为 null
,因为挂载前没有元素。HTMLElement
或具体的 DOM 元素类型,如 HTMLInputElement
。Ref
是一个TypeScript 类型接口:
ref()
函数返回的对象的类型结构。Ref
是泛型接口,通过泛型参数指定存储的数据类型。interface Ref
import { Ref, ref } from 'vue'
// 显式指定count的类型为Ref
名称
类型
用途
ref()
函数
创建一个响应式的数据对象
Ref
TypeScript 泛型接口
描述
ref()
返回的数据的类型结构2、
reactive()
reactive()
是一个核心的 API,用于创建一个响应式对象或数组。
reactive()
会将一个普通 JavaScript 对象或数组转换为响应式代理对象(Proxy)。 1、
reactive()
基本用法import { reactive } from 'vue'
// 创建一个响应式对象
const state = reactive({
name: 'Vue',
count: 0
})
console.log(state.name) // 输出:Vue
state.count++ // 数据更新,自动触发视图更新
2、在模板中使用:
reactive()
创建的对象属性能直接访问:
ref()
不同,reactive()
创建的数据访问时不需要 .value
。
特性
reactive()
ref()
适用类型
对象或数组
基础数据类型(单个值)
访问方式
不需要
.value
必须
.value
本质
Proxy代理对象
包裹基本值的响应式对象
适用场景
复杂对象、数组
单一基本类型数据
3、reactive()
深层响应式特性
reactive()
创建的响应式对象默认都是深层响应式的。
import { reactive } from 'vue'
const state = reactive({
user: {
name: 'Tom',
info: {
age: 20,
hobbies: ['篮球', '音乐']
}
}
})
state
整个对象树都是响应式的: state.user.name = 'Jerry' // ✔️ 响应式更新
state.user.info.age = 21 // ✔️ 深层响应式更新
state.user.info.hobbies.push('阅读') // ✔️ 数组响应式更新
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
// 若获取到的值是对象,则递归 Proxy 化,形成深层响应式
if (typeof res === 'object' && res !== null) {
return reactive(res)
}
// 依赖收集
track(target, key)
return res
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
// 触发更新通知
trigger(target, key)
return result
}
})
}
get
)时,若是嵌套对象,则会自动再次调用 reactive()
创建深层 Proxy。state.user.address.city = '北京' // ✔️ 自动响应式
API
功能
深层响应式
reactive()
创建响应式对象
✅ 深层响应式
shallowReactive()
创建浅层响应式对象
❌ 只响应顶层
import { shallowReactive } from 'vue'
const state = shallowReactive({
user: { name: 'Tom', age: 20 }
})
state.user.age = 21 // ❌ 不会触发响应式更新
state.user = { name: 'Jerry', age: 22 } // ✔️ 会触发响应式更新(只监听顶层属性)
reactive()
与解构的注意事项reactive()
创建的响应式对象若直接解构,会导致数据丢失响应性:const state = reactive({ count: 0 })
// 解构后 count 非响应式!
let { count } = state
count++ // 无法触发响应式更新!
toRefs()
进行解构,保持响应式:import { reactive, toRefs } from 'vue'
const state = reactive({ count: 0 })
const { count } = toRefs(state) // 转换为 ref 类型,保持响应式
count.value++ // ✔️ 仍然响应式
4、ref 解包
ref()
创建响应式数据时,数据被包裹在一个具有 .value
属性的对象里。.value
,直接访问真正的数据,这个过程叫做自动解包(Automatic Unwrapping)。
reactive()
创建的对象),如果它的属性值是一个 ref()
,则访问这个属性时,Vue 会自动解包到 .value
。import { ref, reactive } from 'vue'
const count = ref(1)
// reactive 对象包含一个 ref
const state = reactive({
countRef: count
})
// 访问 state.countRef 时,不需要 .value,也能自动解包:
console.log(state.countRef) // 输出:1,而不是 { value: 1 }
state.countRef
本质上是 ref
对象,但 Vue 的响应式代理会自动解包,使你无需显式使用 .value
访问。 为什么会存在额外的 ref 解包?
.value
的存在。
.value
,非常麻烦。reactive()
对象的属性是 ref()
:const nameRef = ref('Tom')
const state = reactive({ name: nameRef })
console.log(state.name) // 自动解包为 'Tom'
需要注意的问题(⚠️重要)
问题1:在赋值新值时仍保持 ref 特性
const countRef = ref(0)
const state = reactive({ count: countRef })
state.count = 10 // 这里本质上相当于 countRef.value = 10
console.log(countRef.value) // 10(依然联动变化)
问题2:解构赋值问题
数组和集合的注意事项
Map
) 中的元素被访问时,它不会被解包:const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
在模板中解包的注意事项
count
和 object
是顶级属性,但 object.id
不是:const count = ref(0)
const object = { id: ref(1) }
//因此,这个表达式按预期工作:
{{ count + 1 }}
//但这个不会:
{{ object.id + 1 }}
//渲染的结果将是 [object Object]1,因为在计算表达式时 object.id 没有被解包,仍然是一个 ref 对象。
//为了解决这个问题,我们可以将 id 解构为一个顶级属性:
const { id } = object
{{ id + 1 }} //渲染的结果将是 2
//注意
//一个需要注意的点是,如果 ref 是文本插值的最终计算值 (即 {{ }} 标签),那么它将被解包,因此以下内容将渲染为 1:
{{ object.id }}
5、为 reactive 标注类型
reactive()
标注类型通常采用以下几种方式:方式一:使用接口(Interface)标注类型(推荐)
import { reactive } from 'vue'
interface User {
name: string
age: number
}
// 明确标注 reactive 对象的类型为 User
const user = reactive
方式二:使用类型别名(type)标注类型
import { reactive } from 'vue'
type Status = 'success' | 'error' | 'loading'
interface ResponseData {
status: Status
data: string[]
}
// 使用类型别名与接口结合
const response = reactive
方式三:显式类型断言(不推荐,偶尔使用)
import { reactive } from 'vue'
const state = reactive({
name: 'Vue',
version: 3
} as { name: string; version: number })
为复杂嵌套数据标注类型的实践示例
import { reactive } from 'vue'
interface Address {
city: string
zipCode: string
}
interface User {
name: string
address: Address
}
const user = reactive
使用泛型标注 reactive 的常见错误
const user: User = reactive({ name: 'Tom', age: 20 }) // ❌ 错误!
reactive()
返回的类型并非 User
本身,而是 User
类型的 Proxy 代理。const user = reactive