仅供学习,有错误请在评论区指出~
1.Vue3源码组织方式变化:使用 TS 重写
Vue2
Vue 2的源码主要使用ES6+编写,基于类的继承和原型链组织代码,在类型检查方面相对薄弱。Vue 2中的类型仅通过JSDoc注释或Flow进行简单标注,不支持完整的类型推断和检查。这导致在大型项目中,代码重构和维护较为困难,特别是涉及复杂数据结构时。
// Vue 2 源码结构示例(基于JavaScript + Flow)
export default class Vue {
constructor(options) {
this._init(options)
}
_init(options) {
// 初始化逻辑
}
$mount(el) {
// 挂载逻辑
}
// 其他方法...
}
Vue3
Vue 3完全使用TypeScript重写,采用了基于函数式编程的组织方式,提供了更严格的类型定义和检查。源码结构更加模块化,各个功能以独立包的形式发布,可以按需引入。TypeScript的静态类型系统使得代码更加健壮,能够在编译时捕获类型错误,提供了更好的IDE支持,如自动补全和类型提示。
// Vue 3 源码结构示例(基于TypeScript)
// 核心运行时
export interface App<HostElement = any> {
mount(rootContainer: HostElement | string): ComponentPublicInstance
unmount(): void
// 其他属性和方法...
}
export const createApp = <HostElement>(
rootComponent: Component,
rootProps?: Data | null
): App<HostElement> => {
// 创建应用逻辑
return app
}
// 响应式系统
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> {
// 实现响应式逻辑
}
// 组件实现
export function defineComponent<Props, RawBindings>(
setup: (props: Readonly<Props>) => RawBindings | RenderFunction
): Component<Props>
解释
- Vue 2 使用纯 JavaScript,类型检查需要额外工具。
- Vue 3 使用 TypeScript 编写,支持类型推断和静态检查。
2.Vue3支持 Composition API,Vue2只能用Options API
Vue 2 实现 (Options API)
Vue 2主要使用Options API组织组件,将组件的不同功能部分分散到不同的选项中:data、methods、computed、watch等。这种方式使得同一个逻辑关注点的代码被分散到不同位置,在大型组件中难以维护。
export default {
data() {
return {
count: 0
};
},
methods: {
increment() {
this.count++;
}
}
};
Vue 3 实现 (Composition API)
Vue 3引入了Composition API,允许开发者按照逻辑关注点组织代码。相关的状态和方法可以组合在一起,形成可复用的逻辑单元。这种方式使得代码更加模块化,便于理解和维护,特别是在大型组件中。Composition API还支持更好的代码复用,通过自定义组合函数(composables)可以在多个组件间共享逻辑
import { ref } from 'vue';
export default {
setup() {
const count = ref(0);
const increment = () => {
count.value++;
};
return { count, increment };
}
};
解释:
- Vue 2 使用 Options API,逻辑分散在
data
、methods
等选项中。 - Vue 3 使用 Composition API,将相关逻辑集中在一个函数中,更灵活且易于复用。
3. 响应式系统提升
Vue 2 实现 (Object.defineProperty)
Vue 2使用Object.defineProperty
实现响应式系统,通过劫持对象属性的getter和setter来追踪依赖和触发更新。这种实现方式有几个显著的限制:无法检测对象属性的添加或删除;无法直接检测数组索引的变化和长度的变化;需要对每个属性单独处理,性能开销较大。
export default {
data() {
return {
state: {
count: 0
}
};
},
mounted() {
// 无法监听新增属性
this.state.newProperty = 42; // 不会触发视图更新
}
};
Vue 3 实现 (Proxy)
Vue 3使用ES6的Proxy
实现响应式系统,可以拦截对象的多种操作,包括属性访问、属性设置、新增属性、删除属性等。这种实现方式克服了Vue 2的限制,可以监听到对象属性的动态添加和删除,以及数组的索引变化和长度变化,而且性能更好,因为Proxy是对整个对象进行代理,而不是对每个属性。
import { reactive } from 'vue';
export default {
setup() {
const state = reactive({ count: 0 });
state.newProperty = 42; // 可以监听到新属性并触发视图更新
return { state };
}
};
解释:
- Vue 2 使用
Object.defineProperty
,无法监听动态添加或删除的属性。 - Vue 3 使用
Proxy
,可以监听对象的所有变化(包括新增和删除属性)。
4. 编译优化:静态节点标记与提升
Vue 2 实现
Vue 2的模板编译过程会识别静态根节点(包含至少一个静态子节点的节点),并在渲染函数中进行标记,但优化有限。在更新过程中,虚拟DOM的diff算法仍需要遍历整个组件树,包括静态内容,只是对标记为静态的子树跳过详细比对
// Vue 2编译后的渲染函数示例
export default {
render(h) {
return h('div', [
h('h1', this.title),
// 静态节点标记,但仍然会创建VNode
h('p', { staticClass: 'static-text' }, '这是静态文本'),
h('div', { staticClass: 'static-container' }, [
h('p', '另一个静态文本')
]),
h('p', this.dynamicMessage)
])
}
}
// Vue 2的更新过程仍然需要遍历整个组件树
function updateComponent() {
// 生成新的VNode树
const vnode = vm._render()
// 对比新旧VNode树,包括静态部分
vm._update(vnode)
}
Vue 3 实现
Vue 3引入了更先进的编译优化策略,包括静态节点提升、补丁标记和块级树结构。静态节点提升将不变的内容在编译时提取出来,只创建一次;补丁标记对动态内容进行标记,只更新有变化的部分;块级树结构则将模板分割成带有动态内容的块,使得更新时只需要遍历动态内容,大大减少了比对的节点数量。
// Vue 3编译后的渲染函数示例
import { createVNode, createStaticVNode, openBlock, createBlock } from 'vue'
// 静态内容只创建一次,在整个组件生命周期内复用
const _hoisted_1 = /*#__PURE__*/createStaticVNode("<p class=\"static-text\">这是静态文本</p><div class=\"static-container\"><p>另一个静态文本</p></div>", 2)
export default {
setup() {
return { title, dynamicMessage }
},
render(_ctx, _cache) {
return (openBlock(), createBlock("div", null, [
// 动态内容使用PatchFlag标记具体需要更新的部分
createVNode("h1", null, _ctx.title, 1 /* TEXT */),
// 静态内容被提升,不参与更新过程
_hoisted_1,
// 动态内容使用PatchFlag标记
createVNode("p", null, _ctx.dynamicMessage, 1 /* TEXT */)
]))
}
}
// Vue 3的更新过程只需要处理动态内容
function updateComponent() {
// 只对带有PatchFlag的节点进行更新
// 静态内容(_hoisted_1)被跳过
}
解释:
- Vue 2虽然能标记静态根节点,但每次更新仍会重新创建虚拟节点并遍历整个组件树;Vue 3引入了静态节点提升(只创建一次)、补丁标记(精确标记动态内容类型)和块级树结构(只遍历动态内容),使得更新性能显著提升,特别是在大型应用中。Vue 3的编译优化使得模板渲染性能接近手写渲染函数的性能,同时保持了模板的开发便利性。
5. 生命周期的变化:使用setup代替了之前的beforeCreate和created
Vue 2 实现
Vue 2中有八个主要的生命周期钩子,按调用顺序依次为:beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy、destroyed。这些钩子函数作为组件选项直接定义,在组件实例的特定阶段被调用。
// Vue 2生命周期钩子示例
export default {
data() {
return {
message: 'Hello Vue 2'
}
},
// 组件实例刚被创建,还没有初始化数据和事件
beforeCreate() {
console.log('beforeCreate hook')
// 此时无法访问数据和方法
console.log('message:', this.message) // undefined
},
// 组件实例已创建,数据已绑定,但DOM还未挂载
created() {
console.log('created hook')
// 可以访问数据和方法
console.log('message:', this.message) // "Hello Vue 2"
// 可以进行数据请求、事件监听等操作
this.fetchData()
},
// DOM挂载前调用
beforeMount() {
console.log('beforeMount hook')
},
// DOM挂载完成后调用
mounted() {
console.log('mounted hook')
// 可以操作DOM
console.log(this.$el)
},
// 数据更新时,虚拟DOM重新渲染前调用
beforeUpdate() {
console.log('beforeUpdate hook')
},
// 虚拟DOM重新渲染后调用
updated() {
console.log('updated hook')
},
// 组件销毁前调用
beforeDestroy() {
console.log('beforeDestroy hook')
// 清理工作:解除事件监听、取消定时器等
this.cleanup()
},
// 组件销毁后调用
destroyed() {
console.log('destroyed hook')
},
methods: {
fetchData() {
// 数据获取逻辑
},
cleanup() {
// 清理逻辑
}
}
}
Vue 3 实现
Vue 3中,生命周期钩子通过从Vue导入的函数使用,在setup函数中注册。beforeCreate和created钩子被setup函数本身替代,其他钩子则有对应的函数:onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount(替代beforeDestroy)、onUnmounted(替代destroyed)。此外,Vue 3还新增了组合式API特有的钩子,如onRenderTracked和onRenderTriggered。
// Vue 3生命周期钩子示例
import {
ref,
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onRenderTracked,
onRenderTriggered
} from 'vue'
export default {
// setup函数在组件实例创建之初被调用,相当于beforeCreate和created的结合
setup() {
console.log('setup function - replaces beforeCreate & created')
const message = ref('Hello Vue 3')
// 在这里可以访问响应式数据和方法
console.log('message:', message.value) // "Hello Vue 3"
// 可以进行数据请求、事件监听等操作
fetchData()
// DOM挂载前调用
onBeforeMount(() => {
console.log('onBeforeMount hook')
})
// DOM挂载完成后调用
onMounted(() => {
console.log('onMounted hook')
// 可以操作DOM
console.log(document.querySelector('#app'))
})
// 数据更新时,虚拟DOM重新渲染前调用
onBeforeUpdate(() => {
console.log('onBeforeUpdate hook')
})
// 虚拟DOM重新渲染后调用
onUpdated(() => {
console.log('onUpdated hook')
})
// 组件卸载前调用(替代beforeDestroy)
onBeforeUnmount(() => {
console.log('onBeforeUnmount hook')
// 清理工作:解除事件监听、取消定时器等
cleanup()
})
// 组件卸载后调用(替代destroyed)
onUnmounted(() => {
console.log('onUnmounted hook')
})
// Vue 3新增:当组件的渲染依赖项被追踪时调用
onRenderTracked((event) => {
console.log('onRenderTracked', event)
})
// Vue 3新增:当组件的渲染依赖项触发重新渲染时调用
onRenderTriggered((event) => {
console.log('onRenderTriggered', event)
})
function fetchData() {
// 数据获取逻辑
}
function cleanup() {
// 清理逻辑
}
// 必须返回要暴露给模板的内容
return {
message,
fetchData
}
}
}
解释:Vue 2使用选项式API定义生命周期钩子,有beforeCreate、created等八个主要钩子;Vue 3通过组合式API中的函数注册生命周期钩子,setup函数替代了beforeCreate和created,其他钩子改为onBeforeMount、onMounted等形式,销毁相关钩子改名为Unmount,并新增了渲染追踪相关钩子。这种变化使得生命周期钩子可以在逻辑关注点中组织,而不是分散在不同选项中,提高了代码的可组织性和复用性。
6.打包体积优化:移除了一些不常用的API
Vue 2 实现
Vue 2将所有功能打包在一起,即使不使用某些功能,它们也会被包含在最终的构建中。Vue 2支持的功能如过滤器(filters)、内联模板(inline-template)等在单一包中提供,无法通过摇树优化移除未使用的功能。
// Vue 2中的过滤器使用示例
export default {
data() {
return {
price: 100
}
},
// 定义过滤器
filters: {
currency(value) {
return '$' + value.toFixed(2)
}
},
// 使用内联模板
template: `
<div>
<p>价格: {{ price | currency }}</p>
<child-component inline-template>
<div>
<!-- 这里可以访问子组件的数据 -->
<p>我是子组件的内容</p>
</div>
</child-component>
</div>
`
}
Vue 3 实现
Vue 3采用了基于ES模块的模块化架构,支持摇树优化(tree-shaking),未使用的功能可以在构建时被移除,减小最终的包大小。同时,Vue 3移除了一些不常用或有更好替代方案的功能,如过滤器(推荐使用计算属性或方法)和内联模板(推荐使用作用域插槽)。
// Vue 3中不再支持过滤器,改用计算属性或方法
import { ref, computed } from 'vue'
export default {
setup() {
const price = ref(100)
// 使用计算属性替代过滤器
const formattedPrice = computed(() => {
return '$' + price.value.toFixed(2)
})
// 或使用方法
function formatCurrency(value) {
return '$' + value.toFixed(2)
}
return {
price,
formattedPrice,
formatCurrency
}
},
// 使用作用域插槽替代内联模板
template: `
<div>
<p>价格: {{ formattedPrice }}</p>
<child-component>
<template #default="slotProps">
<!-- 通过插槽props访问子组件数据 -->
<div>
<p>我是子组件的内容</p>
</div>
</template>
</child-component>
</div>
`
}
Vue 2将所有功能打包在一起,支持filters、inline-template等特性,但包体积较大且无法优化;Vue 3采用模块化设计和tree-shaking技术,移除了不常用API(如filters、inline-template),并提供了更好的替代方案,使得最终打包体积更小,加载性能更好。Vue 3的全局API也改为了显式导入的方式,进一步支持了tree-shaking优化。
7. Vuex状态管理
Vue 2 实现
Vue 2中使用Vuex需要先通过Vue.use(Vuex)注册插件,然后使用new Vuex.Store()创建store实例。在组件中,通过this.$store访问store实例,使用mapState、mapGetters等辅助函数将store中的状态映射到组件中。
// store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0,
todos: []
},
mutations: {
increment(state) {
state.count++
},
addTodo(state, todo) {
state.todos.push(todo)
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},
fetchTodos({ commit }) {
return fetch('/api/todos')
.then(response => response.json())
.then(todos => {
todos.forEach(todo => commit('addTodo', todo))
})
}
},
getters: {
completedTodos: state => {
return state.todos.filter(todo => todo.completed)
},
todoCount: state => {
return state.todos.length
}
}
})
// main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store, // 将store注入到Vue实例
render: h => h(App)
}).$mount('#app')
// 组件中使用
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex'
export default {
computed: {
// 直接访问store
localCount() {
return this.$store.state.count
},
// 使用mapState辅助函数
...mapState({
count: state => state.count,
todos: 'todos' // 字符串形式,等同于 state => state.todos
}),
// 使用mapGetters辅助函数
...mapGetters(['completedTodos', 'todoCount'])
},
methods: {
// 直接访问store
incrementDirect() {
this.$store.commit('increment')
},
fetchTodosDirect() {
this.$store.dispatch('fetchTodos')
},
// 使用mapMutations辅助函数
...mapMutations(['increment', 'addTodo']),
// 使用mapActions辅助函数
...mapActions(['incrementAsync', 'fetchTodos'])
}
}
Vue 3 实现
Vue 3中,Vuex 4利用Vue 3的新特性,通过createStore函数创建store实例,并通过app.use(store)将其安装到应用中。在组件中,使用useStore()组合式函数获取store实例,结合组合式API使用。
// store.js
import { createStore } from 'vuex'
export const store = createStore({
state() {
return {
count: 0,
todos: []
}
},
mutations: {
increment(state) {
state.count++
},
addTodo(state, todo) {
state.todos.push(todo)
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment')
}, 1000)
},
fetchTodos({ commit }) {
return fetch('/api/todos')
.then(response => response.json())
.then(todos => {
todos.forEach(todo => commit('addTodo', todo))
})
}
},
getters: {
completedTodos: state => {
return state.todos.filter(todo => todo.completed)
},
todoCount: state => {
return state.todos.length
}
}
})
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { store } from './store'
const app = createApp(App)
app.use(store) // 新的API风格
app.mount('#app')
// 组件中使用 - 选项式API
export default {
computed: {
// 可以继续使用mapState等辅助函数
...mapState(['count', 'todos']),
...mapGetters(['completedTodos', 'todoCount'])
},
methods: {
...mapMutations(['increment']),
...mapActions(['fetchTodos'])
}
}
// 组件中使用 - 组合式API
import { useStore } from 'vuex'
import { computed } from 'vue'
export default {
setup() {
const store = useStore()
// 访问state
const count = computed(() => store.state.count)
const todos = computed(() => store.state.todos)
// 访问getters
const completedTodos = computed(() => store.getters.completedTodos)
const todoCount = computed(() => store.getters.todoCount)
// 使用mutations
function increment() {
store.commit('increment')
}
// 使用actions
function fetchTodos() {
store.dispatch('fetchTodos')
}
return {
count,
todos,
completedTodos,
todoCount,
increment,
fetchTodos
}
}
}
解释:Vue 2使用new Vuex.Store()创建store实例,通过this.$store访问,偏向选项式API;Vue 3使用createStore()创建实例,通过useStore()函数在组合式API中获取store,这与Vue 3整体的API风格保持一致,更适合函数式编程和组合逻辑。虽然API形式有变化,但Vuex的核心概念(state、getters、mutations、actions)在Vue 3中保持不变,确保了平滑升级。
8. Route 获取页面实例与路由信息:组合式API的访问方式
Vue 2 实现
在Vue 2中,路由实例通过this.router访问,当前路由信息通过this.router访问,当前路由信息通过this. router访问,当前路由信息通过this.route访问。这与Vue 2的选项式API保持一致,依赖于组件实例上下文。
// Vue 2中访问路由
export default {
created() {
// 访问当前路由信息
console.log('Current path:', this.$route.path)
console.log('Route params:', this.$route.params)
console.log('Query string:', this.$route.query)
// 路由导航
this.goToHome()
},
methods: {
goToHome() {
this.$router.push('/home')
},
goBack() {
this.$router.go(-1)
},
navigateWithParams() {
this.$router.push({
name: 'user',
params: { id: 123 },
query: { plan: 'premium' }
})
},
navigateWithReplace() {
// 替换当前历史记录,而不是添加新记录
this.$router.replace('/login')
}
},
// 利用路由信息计算属性
computed: {
isHomePage() {
return this.$route.path === '/home'
},
userName() {
return this.$route.params.username
},
userQueryParams() {
return this.$route.query
}
},
// 监听路由变化
watch: {
'$route'(to, from) {
console.log('Route changed from', from.path, 'to', to.path)
// 执行页面特定逻辑
this.loadPageData()
}
}
}
Vue 3 实现
Vue 3中,为了适应组合式API,Vue Router 4提供了useRouter和useRoute组合函数来获取路由实例和当前路由信息。这些函数只能在setup函数或组合式函数中使用,符合Vue 3的设计理念。
// Vue 3中访问路由
import { computed, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
export default {
setup() {
// 获取路由实例和当前路由
const router = useRouter()
const route = useRoute()
// 访问当前路由信息
console.log('Current path:', route.path)
console.log('Route params:', route.params)
console.log('Query string:', route.query)
// 路由导航方法
function goToHome() {
router.push('/home')
}
function goBack() {
router.go(-1)
}
function navigateWithParams() {
router.push({
name: 'user',
params: { id: 123 },
query: { plan: 'premium' }
})
}
function navigateWithReplace() {
router.replace('/login')
}
// 利用路由信息的计算属性
const isHomePage = computed(() => route.path === '/home')
const userName = computed(() => route.params.username)
const userQueryParams = computed(() => route.query)
// 监听路由变化
watch(
() => route.path,
(newPath, oldPath) => {
console.log('Route changed from', oldPath, 'to', newPath)
// 执行页面特定逻辑
loadPageData()
}
)
function loadPageData() {
// 加载页面数据
}
// 需要返回以供模板使用
return {
route, // 可以在模板中使用route.path等
goToHome,
goBack,
navigateWithParams,
navigateWithReplace,
isHomePage,
userName,
userQueryParams
}
}
}
// 还可以提取为可复用的组合函数
function useNavigation() {
const router = useRouter()
function goToHome() {
router.push('/home')
}
function goToUser(id) {
router.push({ name: 'user', params: { id } })
}
return {
goToHome,
goToUser
}
}
Vue 2通过this.router和this.router和this. router和this.route实例属性访问路由,符合选项式API风格;Vue 3引入useRouter和useRoute组合函数获取路由,符合组合式API风格,便于逻辑复用和提取。两者在功能上基本等同,但API设计理念不同,Vue 3的方式更加灵活,可以结合其他组合式函数创建复杂的导航逻辑。Vue 3也保留了对选项式API的支持,仍可使用this.router和this.router和this. router和this.route,确保平滑迁移。
9. Props 的使用变化:访问方式不同
Vue2实现
Vue 2中,props定义在props选项中,通过this访问props数据,这与访问data和computed等其他选项中定义的数据方式保持一致。
// Vue 2中的props
export default {
props: {
// 简单类型检查
title: String,
// 详细类型定义
user: {
type: Object,
required: true
},
// 带默认值
isAdmin: {
type: Boolean,
default: false
},
// 自定义验证
level: {
type: Number,
validator: function(value) {
return value >= 0 && value <= 100
}
}
},
data() {
return {
localTitle: this.title // 基于props初始化本地状态
}
},
computed: {
formattedTitle() {
return this.title.toUpperCase() // 通过this访问props
},
userName() {
return this.user ? this.user.name : 'Anonymous' // 通过this访问props
}
},
methods: {
logProps() {
console.log('Title:', this.title) // 通过this访问props
console.log('User:', this.user)
console.log('Is Admin:', this.isAdmin)
},
handleClick() {
if (this.isAdmin) {
// 管理员逻辑
} else {
// 普通用户逻辑
}
}
},
watch: {
// 监听props变化
title(newValue, oldValue) {
console.log('Title changed from:', oldValue, 'to:', newValue)
this.localTitle = newValue // 更新本地状态
}
}
}
Vue 3 实现
Vue 3中,在使用组合式API时,props作为setup函数的第一个参数传入,直接通过props对象访问,不再通过this。这与Vue 3整体的设计理念一致,避免使用this指向组件实例。
// Vue 3中的props
import { ref, computed, watch, toRefs } from 'vue'
export default {
props: {
// props定义方式与Vue 2相同
title: String,
user: {
type: Object,
required: true
},
isAdmin: {
type: Boolean,
default: false
},
level: {
type: Number,
validator: value => value >= 0 && value <= 100
}
},
setup(props) {
// 直接通过props参数访问props
console.log('Title:', props.title)
console.log('User:', props.user)
// 创建本地响应式状态
const localTitle = ref(props.title)
// 使用计算属性处理props
const formattedTitle = computed(() => props.title.toUpperCase())
const userName = computed(() => props.user ? props.user.name : 'Anonymous')
// 方法中访问props
function logProps() {
console.log('Title:', props.title)
console.log('User:', props.user)
console.log('Is Admin:', props.isAdmin)
}
function handleClick() {
if (props.isAdmin) {
// 管理员逻辑
} else {
// 普通用户逻辑
}
}
// 监听props变化
watch(
() => props.title,
(newValue, oldValue) => {
console.log('Title changed from:', oldValue, 'to:', newValue)
localTitle.value = newValue // 更新本地状态
}
)
// 使用toRefs解构props,保持响应性
const { isAdmin, level } = toRefs(props)
// isAdmin是一个ref,需要通过.value访问
const adminMessage = computed(() => {
return isAdmin.value ? '你是管理员' : '你是普通用户'
})
return {
localTitle,
formattedTitle,
userName,
logProps,
handleClick,
adminMessage
}
}
}
Vue 2通过this关键字访问props,与访问data、computed等选项的方式一致,适合Options API;Vue 3在setup函数中通过props参数直接访问props,不依赖this,更加明确和直观,适合Composition API。此外,Vue 3提供了toRefs工具函数,可以将props对象的各个属性转为单独的ref,便于解构使用,同时保持响应性。尽管访问方式有所不同,但props的定义方式(类型声明、默认值、验证等)在Vue 3中基本保持不变。
10. 父子组件传值与emits定义
Vue2实现
Vue 2中,子组件通过$emit方法触发事件,向父组件传递数据,但没有显式声明可触发的事件列表。
// Vue 2 子组件
export default {
data() {
return {
searchText: '',
selectedItem: null
}
},
methods: {
// 触发事件并传递数据
submitSearch() {
this.$emit('search', this.searchText)
},
selectItem(item) {
this.selectedItem = item
this.$emit('select', item)
},
// 自定义事件名
sendCustomData() {
this.$emit('customEvent', { id: 123, name: 'Custom Data' })
},
// 多参数事件
updateValues(value1, value2) {
this.$emit('update', value1, value2, 'extra info')
}
}
}
// Vue 2 父组件
<template>
<div>
<child-component
@search="handleSearch"
@select="handleSelect"
@customEvent="handleCustom"
@update="handleUpdate"
></child-component>
</div>
</template>
<script>
export default {
methods: {
handleSearch(text) {
console.log('Search for:', text)
},
handleSelect(item) {
console.log('Selected item:', item)
},
handleCustom(data) {
console.log('Custom event data:', data)
},
handleUpdate(val1, val2, extraInfo) {
console.log('Update values:', val1, val2, extraInfo)
}
}
}
</script>
Vue 3 实现
Vue 3引入了emits选项,用于显式声明组件可以触发的自定义事件。这有助于文档和类型推断,并且可以为事件添加验证。在setup函数中,通过context.emit触发事件(通常通过解构获取emit函数)。
// Vue 3 子组件
export default {
// 显式定义可触发的事件
emits: [
'search',
'select',
'customEvent',
'update',
'backData' // 自定义名称的事件需要在这里声明
],
// 带验证的emits
// emits: {
// search: (text) => {
// // 返回true表示验证通过,false表示验证失败
// return typeof text === 'string' && text.length > 0
// },
// select: (item) => {
// return item !== null && typeof item === 'object'
// }
// },
setup(props, { emit }) {
const searchText = ref('')
const selectedItem = ref(null)
// 触发事件并传递数据
function submitSearch() {
emit('search', searchText.value)
}
function selectItem(item) {
selectedItem.value = item
emit('select', item)
}
// 自定义事件名
function sendCustomData() {
emit('customEvent', { id: 123, name: 'Custom Data' })
}
// 自定义名称的事件,需在emits中定义
function sendBackData() {
emit('backData', { result: 'success', timestamp: Date.now() })
}
// 多参数事件
function updateValues(value1, value2) {
emit('update', value1, value2, 'extra info')
}
return {
searchText,
selectedItem,
submitSearch,
selectItem,
sendCustomData,
sendBackData,
updateValues
}
}
}
// Vue 3 父组件
<template>
<div>
<child-component
@search="handleSearch"
@select="handleSelect"
@customEvent="handleCustom"
@backData="handleBackData"
@update="handleUpdate"
></child-component>
</div>
</template>
<script>
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
function handleSearch(text) {
console.log('Search for:', text)
}
function handleSelect(item) {
console.log('Selected item:', item)
}
function handleCustom(data) {
console.log('Custom event data:', data)
}
function handleBackData(data) {
console.log('Back data:', data)
}
function handleUpdate(val1, val2, extraInfo) {
console.log('Update values:', val1, val2, extraInfo)
}
return {
handleSearch,
handleSelect,
handleCustom,
handleBackData,
handleUpdate
}
}
})
</script>
11.Vue3 的 template 模板支持多个根标签
Vue 2 实现
Vue 2的模板必须有且只有一个根节点,导致开发者常常需要添加不必要的包裹div。这一限制源于Vue的虚拟DOM实现,每个组件实例必须对应一个单一的DOM根元素。
<!-- Vue 2: 必须有一个根节点 -->
<template>
<!-- 不能直接并列放置多个元素 -->
<div class="wrapper"> <!-- 额外的包裹元素 -->
<header>头部内容</header>
<main>主要内容</main>
<footer>底部内容</footer>
</div>
</template>
<!-- 错误写法,Vue 2不支持 -->
<template>
<header>头部内容</header>
<main>主要内容</main>
<footer>底部内容</footer>
</template>
Vue 3 实现
Vue 3引入了片段(Fragment)的概念,允许组件拥有多个根节点,无需额外的包裹元素。这一改进使得代码更加简洁,DOM结构更加扁平化,有利于样式控制和性能优化。
<!-- Vue 3: 支持多个根节点 -->
<template>
<header>头部内容</header>
<main>主要内容</main>
<footer>底部内容</footer>
</template>
解释:
Vue 2要求模板必须有一个单一根节点,导致需要添加额外的包裹元素,增加了不必要的DOM嵌套;Vue 3引入Fragment概念,支持多个根节点并行排列,无需额外包裹元素,使DOM结构更扁平,有利于布局实现(特别是CSS Grid和Flexbox布局)和性能优化。这一变化不仅简化了代码,还提高了渲染性能和DOM操作效率。