Vue3 内置属性
Vue3 提供了许多内置属性和方法,这些是框架的核心功能,开发者可以在组件中直接使用它们。
DOM相关属性和方法
$refs
javascript
<template>
<div>
<input ref="inputRef" type="text">
<button ref="buttonRef" @click="focusInput">Focus Input</button>
</div>
</template>
<script>
export default {
methods: {
focusInput() {
// 访问DOM元素
this.$refs.inputRef.focus()
}
}
}
</script>
$attrs 和 $listeners (Vue3中 $listeners 被合并到 $attrs )
javascript
// 父组件
<template>
<ChildComponent
class="parent-class"
@custom-event="handleCustomEvent"
v-bind="$attrs"
/>
</template>
// 子组件
<script>
export default {
inheritAttrs: false, // 不继承根元素的属性
mounted() {
// $attrs包含父组件传递但子组件未声明为props的属性和事件
console.log(this.$attrs) // { class: 'parent-class', onCustomEvent: function }
}
}
</script>
全局属性
app.config.globalProperties
javascript
// main.js
const app = createApp(App)
app.config.globalProperties.$http = axios
// 组件中
export default {
mounted() {
// 在选项式API中访问全局属性
this.$http.get('/api/data')
}
}
组合式API中的内置属性
在组合式API中,我们可以使用以下函数获取内置属性:
useAttrs 和 useSlots
javascript
import { useAttrs, useSlots } from 'vue'
export default {
setup(props, { emit, attrs, slots }) {
// 或者使用hooks
const attrs = useAttrs()
const slots = useSlots()
return { attrs, slots }
}
}
useSlots
javascript
import { useSlots } from 'vue'
export default {
setup() {
const slots = useSlots()
// 检查是否存在特定插槽
const hasDefaultSlot = !!slots.default
// 渲染插槽
const renderSlots = () => {
return slots.default ? slots.default() : null
}
return { hasDefaultSlot, renderSlots }
}
}
Vue3 内置组件
<component> 动态组件
html
<template>
<div>
<component :is="currentComponent" />
</div>
</template>
<script>
export default {
data() {
return {
currentComponent: 'ComponentA'
}
},
components: {
ComponentA,
ComponentB
}
}
</script>
<transition> 过渡组件
html
<template>
<div>
<button @click="show = !show">Toggle</button>
<transition name="fade">
<p v-if="show">Hello</p>
</transition>
</div>
</template>
<style>
.fade-enter-active, .fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
</style>
<keep-alive> 缓存组件
html
<template>
<div>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
</div>
</template>
<slot> 插槽
html
<!-- 父组件 -->
<template>
<ChildComponent>
<template v-slot:header>
<h1>标题内容</h1>
</template>
<p>默认内容</p>
<template v-slot:footer>
<p>底部内容</p>
</template>
</ChildComponent>
</template>
<!-- 子组件 -->
<template>
<div>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
<teleport> 传送组件
html
<template>
<div>
<button @click="showModal = true">显示模态框</button>
<teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<p>这是一个模态框</p>
<button @click="showModal = false">关闭</button>
</div>
</div>
</teleport>
</div>
</template>
Vue 组件实例
在组件中,我们可以通过 this 访问以下内置属性(选项式API中):
$data
javascript
export default {
data() {
return {
message: 'Hello Vue3',
count: 0
}
},
mounted() {
// 访问组件的响应式数据
console.log(this.$data) // { message: 'Hello Vue3', count: 0 }
console.log(this.$data.message) // 'Hello Vue3'
// 通常直接访问
console.log(this.message) // 'Hello Vue3'
}
}
$props
javascript
export default {
props: {
title: String,
initialCount: {
type: Number,
default: 0
}
},
mounted() {
// 访问接收到的props
console.log(this.$props) // { title: '组件标题', initialCount: 5 }
// 通常直接访问
console.log(this.title) // '组件标题'
console.log(this.initialCount) // 5
}
}
$el
javascript
export default {
mounted() {
// 访问组件的根DOM元素
console.log(this.$el) // 组件的根DOM元素
// 可以直接操作DOM(不推荐,除非必要)
this.$el.querySelector('button').focus()
}
}
$options
javascript
export default {
name: 'MyComponent',
data() {
return { message: 'Hello' }
},
methods: {
greet() {
console.log(this.$options.name) // 'MyComponent'
console.log(this.$options.methods) // 所有方法的对象
}
}
}
$parent 和 $root
javascript
export default {
mounted() {
// 访问父组件实例
if (this.$parent) {
console.log(this.$parent)
}
// 访问根组件实例
console.log(this.$root)
}
}
$children (Vue3中已废弃)
Vue3中不再推荐使用 $children ,应该使用 $refs 或其他方式访问子组件。
事件方法
$emit
javascript
export default {
methods: {
notifyParent() {
// 向父组件发射事件
this.$emit('child-event', { data: 'from child' })
}
}
}
$on , $once , $off (Vue3中已被移除)
Vue3中移除了这些方法,推荐使用 mitt 或其他事件总线库。
组件实例方法
$mount
javascript
// 手动挂载实例
const MyComponent = Vue.extend({
template: '<div>Hello</div>'
})
const instance = new MyComponent()
instance.$mount('#app') // 手动挂载到id为app的元素
// 或者
instance.$mount() // 不挂载到DOM,只创建实例
document.body.appendChild(instance.$el) // 手动插入DOM
$forceUpdate
javascript
export default {
data() {
return {
items: []
}
},
methods: {
addItem() {
// 直接修改数组索引,Vue可能无法检测到变化
this.items[0] = 'new item'
// 强制更新视图(不推荐,应该使用Vue.set或数组方法)
this.$forceUpdate()
}
}
}
$nextTick
javascript
export default {
methods: {
updateData() {
this.message = 'Updated message'
// DOM尚未更新
console.log(this.$el.textContent) // 旧内容
// 等待下一次DOM更新
this.$nextTick(() => {
// DOM已更新
console.log(this.$el.textContent) // 'Updated message'
})
}
}
}
Vue3 内置指令
Vue3提供了多个内置指令:
v-if , v-else-if , v-else
html
<template>
<div>
<p v-if="isLoggedIn">欢迎回来</p>
<p v-else>请登录</p>
</div>
</template>
v-show
html
<template>
<div>
<p v-show="isVisible">可见内容</p>
</div>
</template>
v-for
html
<template>
<div>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</div>
</template>
v-model
html
<template>
<div>
<input v-model="message">
<p>{{ message }}</p>
<!-- 自定义组件中使用v-model -->
<CustomComponent v-model:title="pageTitle" />
</div>
</template>
v-bind (简写 : )
html
<template>
<div>
<img :src="imageUrl" :alt="imageAlt">
<!-- 对象语法 -->
<div v-bind="{ id: dynamicId, class: dynamicClass }"></div>
</div>
</template>
v-on (简写 @ )
html
<template>
<div>
<button @click="doSomething">点击</button>
<!-- 事件修饰符 -->
<form @submit.prevent="onSubmit">
<input @keyup.enter="onEnter">
</form>
</div>
</template>
全局API
createApp
javascript
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
defineComponent
javascript
import { defineComponent } from 'vue'
export default defineComponent({
name: 'MyComponent',
props: {
msg: String
},
setup(props) {
return { props }
}
})
ref , reactive , computed , watch 等
javascript
import { ref, reactive, computed, watch } from 'vue'
export default {
setup() {
const count = ref(0)
const state = reactive({ name: 'Vue3' })
const doubled = computed(() => count.value * 2)
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`)
})
return { count, state, doubled }
}
}
Vue3 生命周期钩子
1. Vue3 生命周期钩子概览
| 选项式API (Vue2/Vue3) | 组合式API (Vue3) | 说明 |
|---|---|---|
beforeCreate |
- | 实例初始化后,数据观测和事件配置之前 |
created |
- | 实例创建完成,数据观测、属性、方法等已配置 |
beforeMount |
onBeforeMount |
挂载开始之前 |
mounted |
onMounted |
挂载完成 |
beforeUpdate |
onBeforeUpdate |
数据更新前 |
updated |
onUpdated |
数据更新后 |
beforeUnmount |
onBeforeUnmount |
卸载前 |
unmounted |
onUnmounted |
卸载后 |
| - | onActivated |
被缓存的组件激活时 |
| - | onDeactivated |
被缓存的组件停用时 |
| - | onErrorCaptured |
捕获后代组件的错误 |
| - | onRenderTracked |
跟踪虚拟DOM重新渲染 |
| - | onRenderTriggered |
虚拟DOM重新渲染被触发 |
2. 选项式API中的生命周期钩子
beforeCreate 和 created
javascript
export default {
beforeCreate() {
console.log('beforeCreate: 实例初始化完成')
console.log('此时无法访问data、props、computed等')
// this.message 是 undefined
},
created() {
console.log('created: 实例创建完成')
console.log('此时可以访问data、props、computed等')
console.log('message:', this.message) // 可以访问
// 适合进行数据请求
this.fetchData()
},
data() {
return {
message: 'Hello Vue3',
users: []
}
},
methods: {
fetchData() {
// 获取初始数据
console.log('获取数据')
}
}
}
beforeMount 和 mounted
javascript
export default {
beforeMount() {
console.log('beforeMount: 组件挂载前')
console.log('DOM尚未创建,无法访问DOM元素')
// this.$el 是 undefined
},
mounted() {
console.log('mounted: 组件挂载完成')
console.log('DOM已创建,可以访问DOM元素')
console.log(this.$el) // 组件的根DOM元素
// 适合:
// - 访问DOM元素
// - 启动定时器
// - 添加事件监听器
// - 初始化图表等第三方库
// 初始化图表
this.initChart()
// 添加全局事件监听
window.addEventListener('resize', this.handleResize)
},
methods: {
initChart() {
// 初始化图表代码
console.log('初始化图表')
},
handleResize() {
console.log('窗口大小改变')
}
},
beforeUnmount() {
// 清理工作
window.removeEventListener('resize', this.handleResize)
}
}
beforeUpdate 和 updated
javascript
export default {
data() {
return {
count: 0
}
},
beforeUpdate() {
console.log('beforeUpdate: 数据更新,DOM重新渲染前')
// 可以获取更新前的DOM状态
console.log('当前计数:', this.$el.querySelector('.count').textContent)
},
updated() {
console.log('updated: 数据更新,DOM重新渲染完成')
// 可以获取更新后的DOM状态
console.log('更新后计数:', this.$el.querySelector('.count').textContent)
// 注意:避免在这里修改状态,可能导致无限循环
// this.count++ // ❌ 可能导致无限循环
},
methods: {
increment() {
this.count++
}
}
}
beforeUnmount 和 unmounted
javascript
export default {
mounted() {
// 创建定时器
this.timer = setInterval(() => {
console.log('定时器执行')
}, 1000)
// 创建事件监听
window.addEventListener('scroll', this.handleScroll)
},
beforeUnmount() {
console.log('beforeUnmount: 组件卸载前')
console.log('组件仍然完全可用')
// 适合:
// - 清理定时器
// - 移除事件监听
// - 取消网络请求
// - 清理第三方库实例
// 清理定时器
if (this.timer) {
clearInterval(this.timer)
}
},
unmounted() {
console.log('unmounted: 组件卸载完成')
console.log('组件实例已销毁,大部分属性不可用')
// 清理事件监听
window.removeEventListener('scroll', this.handleScroll)
},
methods: {
handleScroll() {
console.log('滚动事件')
}
}
}
3. 组合式API中的生命周期钩子
基本用法
javascript
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
ref
} from 'vue'
export default {
setup() {
const count = ref(0)
const element = ref(null)
// 相当于 beforeMount
onBeforeMount(() => {
console.log('onBeforeMount: 组件挂载前')
console.log('DOM尚未创建')
// element.value 是 null
})
// 相当于 mounted
onMounted(() => {
console.log('onMounted: 组件挂载完成')
console.log('DOM已创建,可以访问DOM元素')
console.log(element.value) // DOM元素
// 访问DOM元素
element.value.focus()
// 启动定时器
const timer = setInterval(() => {
console.log('定时器执行')
}, 1000)
// 返回清理函数(可选)
return () => {
clearInterval(timer)
}
})
// 相当于 beforeUpdate
onBeforeUpdate(() => {
console.log('onBeforeUpdate: 数据更新,DOM重新渲染前')
})
// 相当于 updated
onUpdated(() => {
console.log('onUpdated: 数据更新,DOM重新渲染完成')
// 同样避免在这里修改状态
})
// 相当于 beforeUnmount
onBeforeUnmount(() => {
console.log('onBeforeUnmount: 组件卸载前')
})
// 相当于 unmounted
onUnmounted(() => {
console.log('onUnmounted: 组件卸载完成')
})
// 多次调用同一个钩子函数
onMounted(() => {
console.log('第二个mounted钩子')
})
return {
count,
element
}
}
}
调试钩子
javascript
import { onRenderTracked, onRenderTriggered, ref } from 'vue'
export default {
setup() {
const count = ref(0)
// 跟踪虚拟DOM重新渲染
onRenderTracked((e) => {
console.log('onRenderTracked:', e)
// e包含:
// - key: 正在跟踪的响应式属性
// - target: 跟踪的对象
// - type: 跟踪的类型 (get, set等)
})
// 虚拟DOM重新渲染被触发
onRenderTriggered((e) => {
console.log('onRenderTriggered:', e)
// 当组件重新渲染时触发,可以帮助调试性能问题
})
return {
count
}
}
}
错误处理钩子
javascript
import { onErrorCaptured, ref } from 'vue'
export default {
setup() {
const error = ref(null)
// 捕获子孙组件的错误
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err)
console.log('错误组件实例:', instance)
console.log('错误信息:', info)
// 存储错误信息
error.value = err.message
// 返回false阻止错误继续向上传播
return false
})
return {
error
}
}
}
4. 生命周期钩子使用场景与最佳实践
数据获取
javascript
export default {
// 选项式API
created() {
// 适合获取初始数据
this.fetchUserData()
},
methods: {
async fetchUserData() {
try {
const response = await fetch('/api/user')
this.user = await response.json()
} catch (error) {
console.error('获取用户数据失败:', error)
}
}
}
}
// 组合式API
import { onMounted, ref } from 'vue'
export default {
setup() {
const user = ref(null)
const loading = ref(false)
const error = ref(null)
// 在onMounted中获取数据
onMounted(async () => {
loading.value = true
try {
const response = await fetch('/api/user')
user.value = await response.json()
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
})
return { user, loading, error }
}
}
DOM操作与第三方库初始化
javascript
export default {
// 选项式API
mounted() {
// 初始化图表库
this.initChart()
// 添加全局事件监听
window.addEventListener('resize', this.handleResize)
},
beforeUnmount() {
// 清理图表实例
if (this.chart) {
this.chart.dispose()
}
// 移除事件监听
window.removeEventListener('resize', this.handleResize)
},
methods: {
initChart() {
// 使用echarts等库初始化图表
this.chart = echarts.init(this.$refs.chartContainer)
// ...
},
handleResize() {
if (this.chart) {
this.chart.resize()
}
}
}
}
// 组合式API
import { onMounted, onBeforeUnmount, ref } from 'vue'
import * as echarts from 'echarts'
export default {
setup() {
const chartContainer = ref(null)
let chart = null
onMounted(() => {
// 初始化图表
chart = echarts.init(chartContainer.value)
// 配置图表
const option = {
// 图表配置
}
chart.setOption(option)
// 添加事件监听
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
// 清理图表实例
if (chart) {
chart.dispose()
chart = null
}
// 移除事件监听
window.removeEventListener('resize', handleResize)
})
const handleResize = () => {
if (chart) {
chart.resize()
}
}
return {
chartContainer
}
}
}
订阅与清理
javascript
export default {
// 选项式API
data() {
return {
subscription: null
}
},
mounted() {
// 订阅数据流
this.subscription = someObservable.subscribe(data => {
this.processData(data)
})
},
beforeUnmount() {
// 取消订阅
if (this.subscription) {
this.subscription.unsubscribe()
}
},
methods: {
processData(data) {
// 处理数据
}
}
}
// 组合式API
import { onMounted, onBeforeUnmount } from 'vue'
export default {
setup() {
let subscription = null
onMounted(() => {
// 订阅数据流
subscription = someObservable.subscribe(data => {
processData(data)
})
})
onBeforeUnmount(() => {
// 取消订阅
if (subscription) {
subscription.unsubscribe()
}
})
const processData = (data) => {
// 处理数据
}
}
}
5. 生命周期钩子注意事项
-
避免在
updated中修改状态javascript// ❌ 错误:可能导致无限循环 updated() { this.count++ } // ✅ 正确:使用计算属性或watch computed: { doubleCount() { return this.count * 2 } } -
在组合式API中可以多次注册同类型钩子
javascriptsetup() { onMounted(() => { console.log('第一个mounted钩子') }) onMounted(() => { console.log('第二个mounted钩子') }) // 两个钩子都会执行 } -
父子组件生命周期顺序
javascript父beforeCreate → 父created → 父beforeMount → 子beforeCreate → 子created → 子beforeMount → 子mounted → 父mounted → 父beforeUpdate → 子beforeUpdate → 子updated → 父updated → 父beforeUnmount → 子beforeUnmount → 子unmounted → 父unmounted -
异步操作和生命周期
javascriptsetup() { onMounted(async () => { // 可以使用async/await const data = await fetchData() console.log(data) }) }