Vue3的基本使用
Vue3的基本使用
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
函数在n
beforeCreate
之前执行一次,所以
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) // 处理可选 prop const 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() {
// 修改基本类型数据直接使用.value
count.value++;
// 对象数据类型只需要第一层.value
person.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. 只读计算属性Getter const doubleCount = computed(() => count.value * 2) // 2. 可读写计算属性Getter + Setter const 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.pageX
y.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 timeout return customRef((track, trigger) => ({ get() { track() return value }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() => { value = newValue trigger() }, delay) } })) } const text = debouncedRef('', 500) </script>
在实际开发中,有以下使用场景:
防抖
Ref
:创建一个防抖 Ref,延迟触发更新。function debouncedRef(initialValue, delay = 200) { let timeout return customRef((track, trigger) => { return { get() { track() return initialValue }, set(newValue) { clearTimeout(timeout) timeout = setTimeout(() => { initialValue = newValue trigger() // 延迟触发更新 }, delay) } } }) } // 使用 const text = debouncedRef('', 500)
异步数据
Ref
:在设置值时执行异步操作,完成后触发更新。function asyncRef(initialValue) { return customRef((track, trigger) => { let value = initialValue return { get() { track() return value }, async set(newValue) { try { const result = await fetchData(newValue) // 假设异步请求 value = result trigger() } 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 = err return 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
......