1. vue3
的基本简介
Vue.js 3
是 Vue
框架的重大版本升级,专注于性能提升、开发体验优化和更好的 TypeScript
支持。其核心特性如下:
特性 | 说明 |
---|---|
Composition API | 引入 setup() 函数和响应式函数(ref , reactive ),逻辑复用更灵活,替代 Options API 的碎片化问题。 |
性能优化 | 虚拟 DOM 重构(Diff 算法优化)、Tree-shaking 支持(按需打包代码),包体积减少 41%。 |
TypeScript 支持 | 源码用 TypeScript 重写,提供完整的类型定义,开发体验更友好。 |
新内置组件 | <Teleport> :跨组件层级渲染内容 - <Suspense> :异步组件加载状态管理 |
Fragment 支持 | 单组件支持多根节点,无需外层包裹 <div> 。 |
2. 创建Vue3
工程
-
vue-cli
创建npm create vue
-
vite
创建npm create vite@latest project-name -- --template vue cd <project-name> npm install // 启动开发服务器 npm run dev
Vue3
项目结构说明:
my-vue-app/
├── src/
│ ├── assets/ # 静态资源(图片、CSS)
│ ├── components/ # 组件目录
│ ├── App.vue # 根组件
│ └── main.js # 应用入口文件
├── index.html # 主 HTML 文件
├── vite.config.js # Vite 配置文件
└── package.json
使用vite
创建vue3
项目和使用vuecli
创建vue3
项目的核心区别如下:
特性 | Vite | Vue CLI |
---|---|---|
底层构建工具 | 基于 ESBuild(开发) + Rollup(生产) | 基于 Webpack |
启动速度 | 极快(毫秒级冷启动) | 较慢(需打包所有模块) |
热更新(HMR) | 极快(按需更新) | 较慢(需重新打包部分模块) |
配置复杂度 | 轻量级,开箱即用 | 复杂,依赖 vue.config.js |
生态兼容性 | 兼容 Rollup 插件生态 | 依赖 Webpack 插件生态 |
浏览器支持 | 默认面向现代浏览器(ES Modules) | 默认兼容旧浏览器(通过 polyfill) |
官方定位 | 下一代前端工具,Vue 3 官方推荐 | 旧版 Vue 的官方脚手架 |
3. 常用的Composition API
3.1 setup
函数
在 Vue 3
中,setup
函数是 Composition API
的核心,用于替代 Vue 2
的 data
、methods
等选项。setup
函数在nbeforeCreate
之前执行一次,所以this
是undefined
,所以在setup
中不能使用this
。setup
函数的作用如下:
-
初始化响应式数据:使用
ref
或reactive
声明响应式变量。 -
定义方法:直接在函数内声明并返回方法。
-
访问组件上下文:通过参数获取
props
、emit
、slots
等。 -
组合逻辑:将相关逻辑组织在一起,提升代码可维护性。
setup
中无法访问this
,所有逻辑通过Composition API
实现。// 基本使用 import { ref, reactive, onMounted } from 'vue';export default {props: {title: String},setup(props, context) {// 响应式数据const count = ref(0);const state = reactive({ name: 'Vue 3' });// 方法const increment = () => {count.value++;};// 生命周期钩子onMounted(() => {console.log('组件已挂载');});// 暴露给模板的数据和方法return {count,state,increment};} };
从上述案例可以看出setup
函数有两个参数,如下:
-
pros
:props
是组件接收的响应式属性,对应组件通过props
选项声明的属性。-
响应式:
props
是响应式对象,修改会导致组件更新。 -
禁止直接解构赋值,否则会丢失响应式,需使用
toRefs
或toRef
。// 父组件通过<Demo msg='你好' school='CQUT'>向子组件传递数据import { toRefs, toRef } from 'vue'// 需要声明 props = ['msg', 'school'] setup(props) {// 解构所有 props 并保持响应性const { title } = toRefs(props)// 处理可选 propconst subtitle = toRef(props, 'subtitle')return { title, subtitle } }
-
只读性:子组件不应直接修改
props
,需通过父组件传递新值。
-
-
context
:提供组件上下文信息,包含三个核心属性:attrs
、slots
和emit
。-
attrs
:包含未在props
中声明的属性,如class
、style
、v-on
监听器等。避免基于attrs
创建副作用,如watch
。 -
slots
:收到的插槽内容,相当于this.$slots
。<Demo @hello="sayHello">// vue3中推荐使用v-slot<template v-slot:aaa><span>你好子组件</span></template> </Demo>
-
emit
:触发自定义事件,替代Vue 2
的this.$emit
。// 父组件 <Demo @hello="sayHello"></Demo> <script>setup() {function sayHello() {} }<script>// 子组件 emits = ['hello'] export default {emits: ['submit'],setup(props, { emit }) {const handleClick = () => {emit('submit', '数据')}return { handleClick }} }
-
3.2 _ref
函数
在 Vue 3
中,ref
是组合式 API
的核心函数,用于创建响应式的引用对象。
<template>count: {{ count }} <button @click="increment">count++</button>str: {{ str }}person.name: {{ person.name }}<hr>person.person_info.number: {{ person.person_info.number}}
</template><script>
import {ref} from "vue";export default {name: 'App',setup(props, context) {const count = ref(0);const str = ref('Hello Vue3');const person = ref({name: 'Tom',age: 18,person_info: {number: '666',}});function increment() {// 修改基本类型数据直接使用.valuecount.value++;// 对象数据类型只需要第一层.valueperson.value.person_info.number = '777';}return {count,str,increment,person};},
};
</script><style scoped></style>
对于ref
函数,接收的数据可以是基本数据类型,也可以是对象数据类型,区别如下:
- 基本数据类型:响应式依然是靠
Object.defineProperty()
的get
和set
完成的。 - 对象数据类型:响应式本质上还是使用
Vue3
中的reactive
,使用Proxy
完成响应式。
3.3 reactive
函数
在 Vue 3
中,reactive
是 Composition API
的核心函数之一,用于创建响应式的对象或数组。reactive
函数不能用于基本数据类型。从现象上来看,reactive
函数封装的对象数据操作不需要.value
。
import { reactive } from 'vue';const state = reactive({name: 'Alice',age: 30,address: {city: 'New York'}
});// 修改属性(自动触发视图更新)
state.age = 31;
state.address.city = 'Los Angeles';// 数组
const list = reactive(['apple', 'banana']);
list[0] = 'orange'; // 触发更新
list.push('grape'); // 触发更新
<template><div><p>Name: {{ state.name }}</p><p>Age: {{ state.age }}</p><p>City: {{ state.address.city }}</p></div>
</template>
reactive
返回原始对象的Proxy
代理,而非原始对象本身。验证如下:
console.log(state === reactive(state)); // true(同一代理)
与ref
对比如下:
特性 | reactive | ref |
---|---|---|
适用类型 | 对象、数组 | 基本类型、对象、数组 |
访问方式 | 直接访问属性(state.name ) | 需 .value (脚本中) |
解构赋值 | 需使用 toRefs 保持响应性 | 需保持 .value 引用 |
模板使用 | 直接使用属性(state.name ) | 自动解包(无需 .value ) |
3.4 computed
计算属性
通过 computed
函数创建计算属性,支持两种形式:
-
Getter
函数,只读 -
对象形式,可读可写,含
get
和set
方法import { ref, computed } from 'vue'export default {setup() {const count = ref(0)// 1. 只读计算属性Getterconst doubleCount = computed(() => count.value * 2)// 2. 可读写计算属性Getter + Setterconst writableCount = computed({get: () => count.value + 1,set: (val) => {count.value = val - 1}})return { count, doubleCount, writableCount }} }
<template><div><p>原始值: {{ count }}</p><p>只读计算值: {{ doubleCount }}</p><p>可写计算值: {{ writableCount }}</p><button @click="writableCount = 10">修改可写计算属性</button></div> </template>
computed
与ref
的区别如下:
特性 | ref | computed |
---|---|---|
数据来源 | 直接赋值 | 依赖其他响应式数据计算得出 |
缓存 | 无 | 有【依赖不变时复用缓存值】 |
可写性 | 可直接修改 .value | 默认只读,需配置 set 方法 |
下面是一些计算属性的使用场景:
-
依赖多个数据源
const firstName = ref('John') const lastName = ref('Doe')const fullName = computed(() => `${firstName.value} ${lastName.value}`)
-
结合
reactive
对象const state = reactive({price: 100,quantity: 2,total: computed(() => state.price * state.quantity) })
-
链式计算属性
const discount = ref(0.8) const discountedTotal = computed(() => state.total * discount.value)
对于vue3
中的计算属性的使用,有以下注意点:
-
不要在计算属性内执行异步操作或副作用,如修改
DOM
、发送请求。副作用应使用watch
或watchEffect
处理。 -
确保计算函数轻量,避免复杂计算。复杂逻辑可拆分为多个计算属性。如果计算属性依赖大量数据,考虑使用
computed
的缓存特性替代method
。 -
解构
reactive
对象中的计算属性会丢失响应性,需用toRefs
。const state = reactive({count: 0,double: computed(() => state.count * 2) })// ❌ 错误:解构后失去响应性 const { double } = state// ✅ 正确:保持响应性 const { double } = toRefs(state)
3.5 watch
监视
在 Vue 3
的 Composition API
中,watch
函数用于观察响应式数据的变化并执行副作用操作,如异步请求、DOM
操作等。语法结构如下:
import { watch } from 'vue'watch(source, // 要监听的数据源 ref/reactive/getter/数组callback, // 变化回调函数options?: { // 可选的配置项immediate?: boolean,deep?: boolean,flush?: 'pre' | 'post' | 'sync'}
)
选项 | 说明 |
---|---|
immediate | 是否立即执行回调,默认 false |
deep | 是否深度监听对象/数组,默认 false |
flush | 回调触发时机: pre :组件更新前【默认】; post :组件更新后 ;sync :同步触发 |
watch
可以监听不同类型的数据源,如下:
-
基本数据类型
setup(props, context) {const count = ref(0);const str = ref('Hello Vue3');function increment() {count.value++;}function changePName() {person.name = 'Jerry';}// 监视单个基本数据类型watch(count, () => {console.log('count changed');})// 监视多个基本数据类型watch([count, str], () => {console.log('count 或 str changed');}) }
-
对象数据类型
-
监听整个
reactive
对象,默认浅层监听。import { reactive, watch } from 'vue'const state = reactive({ name: 'Alice', profile: { age: 25 } })// 浅层监听,只响应对象引用变化 watch(state,(newVal, oldVal) => {console.log('state 变化:', newVal)} )// ❌ 不会触发, 修改嵌套属性,对象引用未变 state.profile.age = 30 // ✅ 触发(替换整个对象) state = reactive({ name: 'Bob' })
-
深度监听嵌套属性,即
deep: true
。在该情况需要注意newVal
和oldVal
会指同一引用。watch(state,(newVal) => {console.log('深度监听:', newVal)},{ deep: true } // 启用深度监听 )// ✅ 触发 修改嵌套属性 state.profile.age = 30
-
监听特定属性
-
使用
getter
函数监听单个属性watch(() => state.name,(newName, oldName) => {console.log('name变化:', oldName, '→', newName)} )// ✅ 触发 state.name = 'Bob'
-
监听嵌套对象属性
watch(() => state.profile.age,(newAge) => {console.log('age变化:', newAge)} )// ✅ 触发 state.profile.age = 30
-
监听多个属性,使用数组形式
watch([() => state.name, () => state.profile.age],([newName, newAge], [oldName, oldAge]) => {console.log(`多个变化: name=${oldName}→${newName}, age=${oldAge}→${newAge}`)} )
-
-
3.6 watchEffect
函数
在 Vue 3
的 Composition API
中,watchEffect
是一个用于自动追踪依赖并执行副作用的函数。它的特点是无需显式声明依赖,而是通过回调函数内部实际使用的响应式数据自动收集依赖。语法结构如下:
import { watchEffect } from 'vue'const stop = watchEffect((onInvalidate) => {// 副作用逻辑(自动追踪依赖)// onInvalidate: 注册清理函数(可选)},{flush?: 'pre' | 'post' | 'sync', // 回调执行时机onTrack?: (event) => void, // 调试依赖追踪onTrigger?: (event) => void // 调试依赖触发}
)// 手动停止监听
stop()
其核心特性如下:
- 自动依赖收集:
watchEffect
会自动追踪回调函数内部使用到的响应式数据。当依赖的数据变化时,回调函数会重新执行。 - 立即执行:默认在初始化时立即执行一次回调,类似
watch
的immediate: true
。 - 无新旧值:回调函数不提供新旧值参数,仅关注当前值。
特性 | watchEffect | watch |
---|---|---|
依赖声明 | 自动收集回调内的依赖 | 显式声明数据源 |
初始执行 | 立即执行 | 需配置 immediate: true |
新旧值 | 无 | 提供 newVal 和 oldVal |
适用场景 | 依赖复杂或需要立即执行的副作用 | 需要精确控制监听源和访问旧值时 |
3.7 Vue3
生命周期
首先是Option API
钩子,如下:
钩子函数 | 执行阶段 |
---|---|
beforeCreate | 组件实例初始化前【数据观测和事件配置未完成】 |
created | 组件实例初始化完成【数据观测完成,但 DOM 未生成】 |
beforeMount | 组件挂载到 DOM 前 |
mounted | 组件挂载到 DOM 后【可访问 DOM 元素】 |
beforeUpdate | 响应式数据变化,DOM 重新渲染前 |
updated | 响应式数据变化,DOM 重新渲染后 |
beforeUnmount | 组件卸载前【替代 Vue 2 的 beforeDestroy 】 |
unmounted | 组件卸载后【替代 Vue 2 的 destroyed 】 |
errorCaptured | 捕获子孙组件错误时触发 |
renderTracked | 渲染函数依赖的响应式数据被追踪时触发【开发模式调试用】 |
renderTriggered | 渲染函数依赖的响应式数据触发重新渲染时【开发模式调试用】 |
上述钩子使用配置项,符合Vue2
风格,在Vue3
中,推荐使用Composition API
,如下::
钩子函数 | 对应 Options API 钩子 |
---|---|
onBeforeMount | beforeMount |
onMounted | mounted |
onBeforeUpdate | beforeUpdate |
onUpdated | updated |
onBeforeUnmount | beforeUnmount |
onUnmounted | unmounted |
onErrorCaptured | errorCaptured |
onRenderTracked | renderTracked |
onRenderTriggered | renderTriggered |
生命周期执行流程图如下:同时使用Option API
钩子和Composition API
优先使用Composition API
。
父组件 setup → 父组件 beforeCreate → 父组件 created → 父组件 beforeMount↓
子组件 setup → 子组件 beforeCreate → 子组件 created → 子组件 beforeMount → 子组件 mounted↓
父组件 mounted → 父组件 beforeUpdate → 子组件 beforeUpdate → 子组件 updated → 父组件 updated↓
父组件 beforeUnmount → 子组件 beforeUnmount → 子组件 unmounted → 父组件 unmounted
在 setup()
函数中,通过 onXxx
函数注册生命周期钩子,如下:
import { onMounted, onUpdated } from 'vue'export default {setup() {// 挂载后操作onMounted(() => {// 异步请求推荐位置const response = await fetch('https://api.example.com/data')data.value = await response.json()timer.value = setInterval(() => {console.log('定时器运行中...')}, 1000)})// 更新后操作onUpdated(() => {clearInterval(timer.value)})}
}
3.8 自定义hook
组合式函数是一个普通 JavaScript
函数,内部使用Vue
的 Composition API
,如 ref
, reactive
,watch
等的封装逻辑。命名通常以 use
开头,如 useFetch
, useMouse
,以便代码可读性。与Mixins
对比如下:
特性 | 组合式函数 | Mixins |
---|---|---|
逻辑复用方式 | 函数调用,显式暴露状态和方法 | 隐式合并到组件选项对象中 |
命名冲突 | 无【通过解构命名控制】 | 容易发生【需手动处理】 |
类型支持 | 天然支持 TypeScript | 类型推断困难 |
代码组织 | 按功能组织,代码集中 | 分散在多个选项中 |
从上述介绍不难看出,自定义hook
就是将setup
中的Composition API
进行了封装,以提高代码复用率。通常建立一个hooks
文件夹,在里面存放自己的hook
。如下面封装一个获取鼠标位置的hook
:
// hooks/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'export function useMouse() {const x = ref(0)const y = ref(0)const update = (event) => {x.value = event.pageXy.value = event.pageY}onMounted(() => window.addEventListener('mousemove', update))onUnmounted(() => window.removeEventListener('mousemove', update))return { x, y }
}
// 在组件中使用
<script setup>
import { useMouse } from './useMouse'// 调用组合式函数,解构响应式状态
const { x, y } = useMouse()
</script><template><div>鼠标位置:{{ x }}, {{ y }}</div>
</template>
3.9 toRef
与toRefs
在 Vue 3
中,toRef
和 toRefs
是用于处理响应式对象属性的工具函数,它们的主要作用是将响应式对象的属性转换为 ref
对象,以保持响应性。它们的核心区别如下:
特性 | toRef | toRefs |
---|---|---|
作用对象 | 响应式对象的单个属性 | 响应式对象的所有属性 |
返回值 | 单个 ref 对象 | 包含所有属性的 ref 对象的普通对象 |
适用场景 | 需要保留对某个属性的响应式引用 | 结构整个响应式对象,保持所有属性的响应性 |
是否处理不存在属性 | 可为不存在的属性创建 ref 【需谨慎】 | 仅处理对象已存在的属性 |
-
toRef
的使用:将响应式对象的某个属性转换为ref
。import { reactive, toRef } from 'vue'const state = reactive({ name: 'Alice', age: 30 }) const nameRef = toRef(state, 'name')// 修改 ref 会同步到原对象 nameRef.value = 'Bob' console.log(state.name) // 输出: 'Bob'
// 处理不存在的属性:若属性不存在,toRef 会创建一个可写的 ref,但不会自动添加到原对象const state = reactive({ name: 'Alice' }) const ageRef = toRef(state, 'age') // 原对象无 age 属性// 修改 ref 的 value ageRef.value = 30 console.log(state.age) // 输出: undefined 原对象未添加该属性
-
toRefs
的使用:将响应式对象的所有属性转换为ref
。import { reactive, toRefs } from 'vue'const state = reactive({ name: 'Alice', age: 30 }) const stateRefs = toRefs(state)// 结构后仍保持响应性 const { name, age } = stateRefs name.value = 'Bob' console.log(state.name) // 输出: 'Bob'
// 处理嵌套对象 const state = reactive({ info: { age: 30 } }) const stateRefs = toRefs(state)// stateRefs.info 是 ref,其 value 是响应式对象 stateRefs.info.value.age = 31 // 响应式更新
那么什么时候使用toRef
,什么时候使用toRefs
呢?
- 单属性引用 →
toRef
- 多属性结构 →
toRefs
4. 其它Composition API
4.1 shallowReactive
与shallowRef
在Vue
3 中,shallowReactive
和 shallowRef
是用于浅层响应式处理的 API,适用于需要优化性能的场景。它们与标准 reactive
和 ref
的关键区别在于仅对顶层数据做响应式处理,不递归追踪深层对象的变化。
-
shallowReactive
:只对对象的第一层属性进行响应式处理,嵌套对象保持原始类型。当使用数据量大的对象,且仅需监听顶层字段变化的场景如配置对象、表单控件集合时使用。若需要响应嵌套对象的变化,需手动替换整个嵌套对象。import { shallowReactive, watchEffect } from 'vue'const state = shallowReactive({name: 'Alice', // 响应式(顶层属性)profile: { // ❌ 非响应式(嵌套对象)age: 25} })// 监听顶层属性变化 watchEffect(() => {console.log('名字变化:', state.name) // ✅ 触发 })// 监听嵌套属性变化 watchEffect(() => {console.log('年龄变化:', state.profile.age) // ❌ 不触发 })// 修改顶层属性 → 触发响应 state.name = 'Bob'// 修改嵌套属性 → 不触发响应 state.profile.age = 30
-
shallowRef
:只处理基本数据类型的响应式,不进行对象的响应式处理。适合场景为存储大型对象,如DOM
元素、复杂数据结构,避免深度响应式开销||
需要整体替换值的场景,如分页数据更新。import { shallowRef, watchEffect } from 'vue'// 存储一个对象 const userRef = shallowRef({name: 'Alice',profile: { age: 25 } })// 监听整个 .value 变化 watchEffect(() => {console.log('用户变化:', userRef.value) // ✅ 触发(当 userRef.value 被替换时) })// 监听嵌套属性变化 watchEffect(() => {console.log('年龄变化:', userRef.value.profile.age) // ❌ 不触发 })// 修改嵌套属性 → 不触发响应 userRef.value.profile.age = 30// 替换整个值 → 触发响应 userRef.value = {name: 'Bob',profile: { age: 30 } }
4.2 readonly
和shallowReadonly
在 Vue 3
中,readonly
和 shallowReadonly
是用于创建只读响应式对象的工具,适用于需要保护数据不被意外修改的场景,如传递 props
、状态管理等。以下是它们的详细用法和区别:
-
readonly
:递归地将整个对象及其嵌套属性变为只读。依然是响应式对象,但不可修改。import { reactive, readonly, watchEffect } from 'vue'const original = reactive({ name: 'Alice', profile: { age: 25 } })const readOnlyObj = readonly(original)// 尝试修改会触发警告(开发环境下)且操作无效 readOnlyObj.name = 'Bob' // ❌ 失败 readOnlyObj.profile.age = 30 // ❌ 失败// 监听只读对象变化(仍会响应原始对象的修改) watchEffect(() => {console.log('只读对象的值:', readOnlyObj.name) // 原始对象修改时触发 })// 修改原始对象 → 只读对象同步更新 original.name = 'Bob' // ✅ watchEffect 会触发
-
shallowReadonly
:仅将对象的顶层属性变为只读,嵌套对象仍可变。顶层属性不可修改,但嵌套对象保持原始响应性。import { reactive, shallowReadonly } from 'vue'const original = reactive({ name: 'Alice', profile: { age: 25 } })const shallowReadOnlyObj = shallowReadonly(original)// 修改顶层属性 → 失败 shallowReadOnlyObj.name = 'Bob' // ❌ 失败// 修改嵌套属性 → 成功 shallowReadOnlyObj.profile.age = 30 // ✅ 成功
4.3 toRaw
和markRaw
在 Vue 3
中,toRaw
和 markRaw
是用于处理响应式对象和原始对象的工具函数,适用于需要绕过响应式系统或优化性能的场景。以下是它们的详细用法和核心区别:
-
toRaw
:返回由reactive
、readonly
等API
创建的响应式对象的原始非响应式对象。用于直接操作原始对象,避免响应式追踪的开销,如一次性批量修改数据。import { reactive, toRaw } from 'vue'const reactiveObj = reactive({ name: 'Alice', profile: { age: 25 } }) const rawObj = toRaw(reactiveObj)// 修改原始对象 → 不会触发响应式更新 rawObj.name = 'Bob' console.log(reactiveObj.name) // 输出: 'Bob'(数据已改,但不会触发视图更新)// 响应式对象仍可触发更新 reactiveObj.name = 'Charlie' // ✅ 触发响应式更新
const rawData = toRaw(reactiveData) rawData.items.push(...newItems) // 避免多次触发更新 // 手动触发更新(如强制重新渲染) triggerRef(reactiveData)
-
makeRaw
:标记一个对象,使其永远不会被转换为响应式对象。避免Vue
对大型对象或第三方实例进行不必要的响应式处理。import { reactive, markRaw } from 'vue'// 标记对象为原始 const staticData = markRaw({ config: { theme: 'dark' }, methods: { /* 复杂方法 */ } })// 即使被包裹在响应式对象中,staticData 仍保持原始 const state = reactive({staticData, // ❌ 不会被代理dynamicData: { count: 0 } // ✅ 会被代理 })// 修改 staticData 的嵌套属性 → 不会触发响应式 state.staticData.config.theme = 'light' console.log(state.staticData.config.theme) // 'light'(数据已改,但无响应式)
特性 | toRaw | markRaw |
---|---|---|
作用对象 | 响应式对象,如 reactive 、ref | 任何对象 |
返回值 | 原始对象 | 被标记的原始对象 |
修改原始对象的影响 | 响应式对象数据同步更新,但不触发响应式追踪 | 对象永远不会被 Vue 代理 |
适用场景 | 临时操作原始数据 | 永久禁止对象被响应式处理 |
4.4 customRef
在 Vue 3
中,customRef
允许开发者创建自定义的响应式引用ref
,通过手动控制依赖追踪track
和更新触发trigger
,适用于需要精细控制响应式行为的场景,如防抖、异步操作等。customRef
接收一个工厂函数,该函数需返回包含 get
和 set
方法的对象:
-
get()
:在访问值时调用,需手动调用track()
追踪依赖。 -
set(newValue)
:在修改值时调用,需手动调用trigger()
触发更新。<template><input v-model="text" /><div>Debounced Value: {{ text }}</div> </template><script setup> import { customRef } from 'vue'function debouncedRef(value, delay = 200) {let timeoutreturn customRef((track, trigger) => ({get() {track()return value},set(newValue) {clearTimeout(timeout)timeout = setTimeout(() => {value = newValuetrigger()}, delay)}})) }const text = debouncedRef('', 500) </script>
在实际开发中,有以下使用场景:
-
防抖
Ref
:创建一个防抖 Ref,延迟触发更新。function debouncedRef(initialValue, delay = 200) {let timeoutreturn customRef((track, trigger) => {return {get() {track()return initialValue},set(newValue) {clearTimeout(timeout)timeout = setTimeout(() => {initialValue = newValuetrigger() // 延迟触发更新}, delay)}}}) }// 使用 const text = debouncedRef('', 500)
-
异步数据
Ref
:在设置值时执行异步操作,完成后触发更新。function asyncRef(initialValue) {return customRef((track, trigger) => {let value = initialValuereturn {get() {track()return value},async set(newValue) {try {const result = await fetchData(newValue) // 假设异步请求value = resulttrigger()} catch (error) {console.error('请求失败:', error)}}}}) }// 使用 const data = asyncRef(null)
customRef
适用于需要手动控制响应式行为的场景,如防抖、异步更新、集成第三方库等。
4.5 provide
与inject
在 Vue 3
中,provide
和 inject
用于实现跨组件层级的数据传递,尤其适合祖先组件与深层嵌套的后代组件之间的通信。以下是详细的使用指南和最佳实践:
-
provide
:父组件提供数据,在祖先组件的setup
函数中,使用provide
提供数据。// ParentComponent.vue import { provide, ref } from 'vue'export default {setup() {const message = ref('Hello from parent')provide('message', message) // 提供响应式数据return { message }} }
-
inject
:子组件注入数据,在后代组件的setup
函数中,使用inject
获取数据。// ChildComponent.vue import { inject } from 'vue'export default {setup() {const message = inject('message', '默认值') // 注入数据,可设置默认值return { message }} }
当然,也可以实现响应式数据传递,如下::
-
提供响应式数据:确保提供的数据是响应式【
ref
或reactive
】,以便子组件能检测到变化。// ParentComponent.vue const count = ref(0) provide('count', count)
-
在子组件中修改数据:若需子组件修改数据,可在父组件中提供方法。
// ParentComponent.vue const increment = () => count.value++ provide('count', { count, increment })
// ChildComponent.vue const { count, increment } = inject('count')
对于上述两个API
的使用,需要注意以下问题:
-
响应式数据必须显式提供:若提供非响应式数据,子组件无法检测变化。
provide('staticData', { key: 'value' }) // ❌ 非响应式 provide('dynamicData', ref({ key: 'value' })) // ✅ 响应式
-
单向数据流:避免子组件直接修改注入的响应式数据,除非父组件明确允许。
// ParentComponent.vue provide('data', readonly(data)) // 使用 readonly 保护数据
4.6 响应式数据的判断
isRef
:检查一个值是否为一个Ref
对象。isReactive
:检查一个值是否由reactive
创建的响应式代理。isReadonly
:检查一个对象是否由readonly
创建的只读代理。isProxy
:检查一个对象是否由reactive
或readonly
方法创建的代理。
5. Fragment
组件
在 Vue 3
中,Fragment
片段是一项重要的新特性,允许组件模板包含多个根节点而无需包裹在一个父容器中。
<!-- 合法模板(Vue 3) -->
<template><h1>标题</h1><p>内容</p><button>按钮</button>
</template>
6. Teleport
组件
在 Vue 3
中,<Teleport>
组件用于将模板内容渲染到DOM
中的指定位置,常用于解决组件层级导致的样式或布局问题,如模态框、全局通知等。基础用法用法如下:
-
定义目标容器:在
HTML
中添加目标容器,通常位于根节点附近。<!-- public/index.html --> <body><div id="app"></div><div id="modal-container"></div> <!-- Teleport 目标容器 --> </body>
-
在组件中使用
<Teleport>
。<!-- MyComponent.vue --> <template><button @click="showModal = true">打开模态框</button><Teleport to="#modal-container"><div v-if="showModal" class="modal"><h2>标题</h2><p>内容...</p><button @click="showModal = false">关闭</button></div></Teleport> </template><script setup> import { ref } from 'vue' const showModal = ref(false) </script>
动态目标与禁用撞状态:
-
动态切换目标容器
<Teleport :to="isMobile ? '#mobile-modal' : '#desktop-modal'"><!-- 内容 --> </Teleport>
-
禁用
Teleport
:通过:disabled
控制内容是否保留在原位置。<Teleport to="#modal-container" :disabled="forceInline"><!-- 当 forceInline 为 true 时,内容不传送 --> </Teleport>
与Transition
组件结合使用:
<Teleport to="#modal-container"><Transition name="fade"><div v-if="showModal" class="modal"><!-- 内容 --></div></Transition>
</Teleport><style>
.fade-enter-active, .fade-leave-active {transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {opacity: 0;
}
</style>
多内容传送与顺序:多个 Teleport 内容按代码顺序渲染到目标容器。
<Teleport to="#notifications"><div class="notification">通知 1</div>
</Teleport><Teleport to="#notifications"><div class="notification">通知 2</div>
</Teleport><!-- 目标容器显示顺序:通知 1 → 通知 2 -->
7. Suspense
组件
在 Vue 3
中,<Suspense>
组件用于优雅地处理异步组件或异步数据的加载状态,允许开发者定义加载中和加载完成后的展示内容。以下是其详细使用指南和常见场景示例:
-
基础用法:当加载异步组件时,展示加载状态。
<template><Suspense><!-- 默认插槽:异步组件 --><template #default><AsyncComponent /></template><!-- 加载中状态 --><template #fallback><div>加载中...</div></template></Suspense> </template><script setup> import { defineAsyncComponent } from 'vue'// 定义异步组件(返回 Promise) const AsyncComponent = defineAsyncComponent(() =>import('./AsyncComponent.vue') ) </script>
-
处理异步数据:在组件
setup
中使用async
函数处理异步数据。<template><Suspense><template #default><UserProfile :user="userData" /></template><template #fallback><div>加载用户数据...</div></template></Suspense> </template><script setup> import { ref } from 'vue'const userData = ref(null)// 异步获取数据(setup 函数支持 async) async function fetchUser() {const response = await fetch('/api/user')userData.value = await response.json() }await fetchUser() // 等待数据加载完成 </script>
-
错误处理
-
捕获异步错误:使用
onErrorCaptured
钩子捕获错误并显示友好提示。<template><div v-if="error">{{ error.message }}</div><Suspense v-else><template #default><AsyncComponent /></template><template #fallback><div>加载中...</div></template></Suspense> </template><script setup> import { ref, onErrorCaptured } from 'vue'const error = ref(null)onErrorCaptured((err) => {error.value = errreturn false // 阻止错误继续向上传播 }) </script>
-
重试机制:允许用户点击重试加载。
<template><div v-if="error"><p>加载失败:{{ error.message }}</p><button @click="retry">重试</button></div><Suspense v-else><!-- ... --></Suspense> </template><script setup> const error = ref(null) const retry = () => {error.value = null// 重新执行异步操作(如重新加载组件或数据) } </script>
-
动态切换异步组件:结合动态组件
<component :is="...">
实现按需加载。<template><button @click="toggleComponent">切换组件</button><Suspense><template #default><component :is="currentComponent" /></template><template #fallback><div>加载组件中...</div></template></Suspense> </template><script setup> import { ref, defineAsyncComponent } from 'vue'const components = {A: defineAsyncComponent(() => import('./ComponentA.vue')),B: defineAsyncComponent(() => import('./ComponentB.vue')) }const currentComponent = ref('A') const toggleComponent = () => {currentComponent.value = currentComponent.value === 'A' ? 'B' : 'A' } </script>
-
8. Vue3
中的其它变化
在Vue3
中,因为Vue
不能使用,将原来的全局API
转移到了应用实例app
上,如下:
2.X全局API | 3.X全局API |
---|---|
Vue.config.xxx | app.config.xxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
除了上述全局API
的转移,还有以下改变:
-
data
选项始终被声明为一个函数。 -
过度类名的更改
Vue 2 类名 Vue 3 类名 说明 .v-enter
.v-enter-from
进入动画的初始状态 .v-leave
.v-leave-from
离开动画的初始状态 .v-enter-to
无变化 进入动画的结束状态 .v-leave-to
无变化 离开动画的结束状态 .v-enter-active
无变化 进入动画的激活状态(过渡属性) .v-leave-active
无变化 离开动画的激活状态 -
移除
keyCode
作为v-on
的修饰符,同时也不再支持config.keyCodes
。 -
移除
v-on.native
修饰符。 -
移除过滤器
filter
-
......