Vue实例:熟悉Vue实例的创建、配置选项(如data、methods、computed、watch等)。
模板语法:掌握Vue的模板语法,包括插值、指令(v-bind、v-model、v-for、v-if等)。
组件通信:了解父子组件通信(props、$emit)、兄弟组件通信(事件总线、Vuex)、跨级组件通信(provide/inject)。
Vuex:理解Vuex的核心概念,如state、getters、mutations、actions、modules,以及如何在项目中使用Vuex进行状态管理。
Vue CLI:熟悉Vue CLI的使用,了解如何创建、配置和管理Vue项目。
自定义指令和插件:了解如何编写自定义指令和插件,扩展Vue的功能。
虚拟DOM:理解Vue的虚拟DOM机制及其优势。
代码分割:掌握如何使用Webpack进行代码分割,减少初始加载时间。
代码规范:了解常见的代码规范和最佳实践,如ESLint、Prettier等工具的使用。
响应式原理:深入理解Vue的响应式原理,包括Object.defineProperty和Proxy的区别。
虚拟DOM diff算法:了解虚拟DOM的diff算法及其优化策略。
SSR(服务器端渲染):了解Vue的SSR原理及其应用场景。
手写Vuex:能够手写一个简单的Vuex实现。
手写Vue Router:能够手写一个简单的Vue Router实现。
第三方库:了解常用的Vue第三方库,如Element UI、Vuetify、Ant Design Vue等。
5.1 Vue 核心知识 Vue 2 和 Vue 3 的主要区别是什么?
Vue 的响应式原理是什么?
虚拟 DOM 和 Diff 算法的工作原理是什么?
5.2 Vue 高级特性 Composition API 的优势是什么?如何使用?
如何在 Vuex 中实现模块化状态管理?
如何在 Vue Router 中实现路由懒加载?
5.3 性能优化 如何优化 Vue 应用的初始加载性能?
如何实现一个虚拟列表?
如何在 Vue 中实现组件缓存?
初始化非响应式数据:在实例初始化之前设置一些非响应式的数据或配置。 插件初始化:在实例创建之前初始化一些第三方插件。
created:在实例创建完成后调用,此时已完成数据观测、属性和方法的运算,但尚未挂载 DOM。 使用场景:
数据初始化:在实例创建完成后初始化数据,例如从 API 获取数据。 事件监听:在实例创建完成后添加事件监听器。
DOM 操作前的准备工作:在挂载 DOM 之前执行一些操作,例如设置初始状态。
mounted:在实例挂载到 DOM 后调用,此时可以访问 DOM 元素。 使用场景:
DOM 操作:在 DOM 挂载完成后操作 DOM 元素,例如初始化第三方库(如图表库、地图库)。 异步操作:在 DOM 挂载完成后执行异步操作,例如从 API 获取数据。
数据更新前的操作:在数据更新导致 DOM 重新渲染之前执行一些操作,例如保存当前状态。
updated:在数据更新导致虚拟 DOM 重新渲染和打补丁之后调用。 使用场景:
DOM 更新后的操作:在数据更新导致 DOM 重新渲染之后执行一些操作,例如更新第三方库。
资源清理:在实例销毁之前清理资源,例如移除事件监听器、取消定时器。 数据保存:在实例销毁之前保存数据,例如保存表单数据到本地存储。
destroyed:在实例销毁后调用,此时所有的事件监听器和子实例都已被移除。 使用场景:
最终清理:在实例销毁后执行最终的清理操作,例如释放内存。
替代 beforeCreate 和 created:在组件创建之前执行初始化逻辑,例如设置响应式数据、调用 API 等。 组合逻辑:使用 Composition API 将相关逻辑组织在一起。
onBeforeMount:在挂载开始之前调用,类似于 Vue 2 的 beforeMount。 使用场景:
DOM 操作前的准备工作:在挂载 DOM 之前执行一些操作,例如设置初始状态。
onMounted:在实例挂载到 DOM 后调用,类似于 Vue 2 的 mounted。 使用场景:
DOM 操作:在 DOM 挂载完成后操作 DOM 元素,例如初始化第三方库(如图表库、地图库)。 异步操作:在 DOM 挂载完成后执行异步操作,例如从 API 获取数据。
数据更新前的操作:在数据更新导致 DOM 重新渲染之前执行一些操作,例如保存当前状态。
onUpdated:在数据更新导致虚拟 DOM 重新渲染和打补丁之后调用,类似于 Vue 2 的 updated。 使用场景:
DOM 更新后的操作:在数据更新导致 DOM 重新渲染之后执行一些操作,例如更新第三方库。
资源清理:在实例销毁之前清理资源,例如移除事件监听器、取消定时器。 数据保存:在实例销毁之前保存数据,例如保存表单数据到本地存储。
onUnmounted:在实例销毁后调用,类似于 Vue 2 的 destroyed。 使用场景:
最终清理:在实例销毁后执行最终的清理操作,例如释放内存。
缓存的组件:在组件激活或停用时执行一些操作。
onDeactivated:在
缓存的组件:在组件激活或停用时执行一些操作。
onErrorCaptured:在捕获到后代组件的错误时调用。 使用场景:
错误处理:在捕获到后代组件的错误时执行一些操作,例如记录错误或显示错误信息。
1、响应式属性解构
以前defineProps解构出来的值并不是响应式,需要通过toRef这种工具来变成响应式,现在vue3.5更新了这方面的不足,现在可以直接从defineProps解构出来的值就是响应式的了。
2、新增useId()
useId() 是一个 API,用于生成在服务器和客户端渲染之间保持稳定的唯一应用程序 ID。这些 ID 可用于生成表单元素和无障碍属性的 ID,并且可以在 SSR 应用程序中使用而不会导致水化不匹配
3、新增onEffectCleanup函数
在组件卸载之前或者下一次watchEffect回调执行之前会自动调用onEffectCleanup函数,有了这个函数后你就不需要在组件的beforeUnmount钩子函数去统一清理一些timer了。
4、新增base watch函数
5、新增onWatcherCleanup函数
在组件卸载之前或者下一次watch回调执行之前会自动调用onWatcherCleanup函数,同样有了这个函数后你就不需要在组件的beforeUnmount钩子函数去统一清理一些timer了
6、新增pause和resume方法
有的场景中我们可能想在“一段时间中暂停一下”,不去执行watch或者watchEffect中的回调。等业务条件满足后再去恢复执行watch或者watchEffect中的回调。在这种场景中pause和resume方法就能派上用场啦。
7、watch的deep选项支持传入数字
在以前deep选项的值要么是false,要么是true,表明是否深度监听一个对象。在3.5中deep选项支持传入数字了,表明监控对象的深度。
1、useTemplateRef()
以前我们在获取dom元素都是用的ref属性,现在官方引出来这个函数来操作我们的dom
2、Lazy Hydration 懒加载水合
异步组件现在可以通过 defineAsyncComponent() API 的 hydrate 选项来控制何时进行水合。(普通开发者用不上)
3、data-allow-mismatch
SSR中有的时候确实在服务端和客户端生成的html不一致,比如在DOM上面渲染当前时间,这种情况是避免不了会出现前面useId例子中的那种警告,此时我们可以使用data-allow-mismatch属性来干掉警告
5、Teleport组件新增defer延迟属性
Teleport组件的作用是将children中的内容传送到指定的位置去
4、内存改进(优化了响应速度)
实际上,Vue 团队特别优化了许多常见的数组方法,使得遍历数组的速度提高了很多,渲染速度变快了。
new Vue()创建 选项对象:在创建Vue实例时,可以传入一个选项对象,包含数据、模板、挂载元素、方法、生命周期钩子等。
插值:使用双大括号{{ }}进行文本插值。 指令:Vue提供了一系列指令,如v-bind(绑定属性)、v-model(双向数据绑定)、v-for(列表渲染)、v-if(条件渲染)等。
单向数据绑定:使用v-bind将数据绑定到DOM属性。 双向数据绑定:使用v-model在表单输入和应用状态之间创建双向数据绑定。
计算属性:计算属性是基于它们的依赖进行缓存的,只有在依赖发生变化时才会重新计算。 侦听器:侦听器用于观察和响应Vue实例上的数据变动。
组件定义:组件是Vue应用的基本构建块,可以复用和组合。 组件通信:父子组件通过props和$emit进行通信。
生命周期钩子:Vue实例在创建、更新和销毁过程中会触发一系列生命周期钩子,如created、mounted、updated、destroyed等。
内置指令:Vue提供了许多内置指令,如v-if、v-for、v-bind、v-on等。 自定义指令:可以注册自定义指令来扩展Vue的功能。
事件监听:使用v-on指令监听DOM事件,并执行JavaScript代码。
过渡效果:Vue提供了过渡和动画的支持,可以通过
和 组件实现。
路由配置:Vue Router用于构建单页面应用(SPA),通过路由配置实现页面之间的切换。
状态管理:Vuex是Vue的状态管理库,用于集中管理应用的所有组件的状态。
Composition API:Vue3引入了Composition API,提供了更灵活的方式来组织和复用代码逻辑。
new Vue()
el:string; 指定Vue实例挂载的DOM元素。可以是CSS选择器或DOM元素。 data:object; Vue实例的数据对象。Vue会将data对象的属性转换为getter/setter,从而使其成为响应式的。 methods:object; 定义Vue实例的方法。这些方法可以在模板中通过事件绑定调用。 computed:object; 定义计算属性。计算属性是基于它们的依赖进行缓存的,只有在依赖发生变化时才会重新计算。 watch:object; 定义侦听器。侦听器用于观察和响应Vue实例上的数据变动。 components:object; 注册局部组件。这些组件只能在当前Vue实例中使用。 filters:object; 定义过滤器。过滤器用于格式化文本,可以在插值和v-bind表达式中使用。 directives:注册自定义指令。自定义指令用于直接操作DOM。 lifecycle hooks:定义生命周期钩子函数。Vue实例在创建、更新和销毁过程中会触发一系列生命周期钩子。
文本插值:使用双大括号{{ }}进行文本插值。Vue会将数据对象的属性值插入到模板中。 原始HTML插值:使用v-html指令插入原始HTML内容。
v-bind:动态绑定HTML属性。可以使用简写:。
<div id="app">
<a v-bind:href="url">Link</a>
<!-- 简写 -->
<a :href="url">Link</a>
</div>
<script>
new Vue({
el: '#app',
data: {
url: 'https://vuejs.org'
}
});
</script>
v-model:实现表单输入和应用状态之间的双向数据绑定。 v-for:基于源数据多次渲染元素或模板块。可以使用v-for指令遍历数组或对象。 v-if / v-else-if / v-else:根据表达式的值条件性地渲染元素。 v-show:根据表达式的值条件性地显示元素。与v-if不同,v-show只是切换元素的display CSS属性。 v-on:监听DOM事件。可以使用简写@。
<div id="app">
<button v-on:click="sayHello">Say Hello</button>
<!-- 简写 -->
<button @click="sayHello">Say Hello</button>
</div>
<script>
new Vue({
el: '#app',
methods: {
sayHello: function () {
alert('Hello Vue!');
}
}
});
</script>
v-pre:跳过这个元素和它的子元素的编译过程。可以用来显示原始Mustache标签。
<div id="app">
<p v-pre>{{ this will not be compiled }}</p>
</div>
<script>
new Vue({
el: '#app'
});
</script>
v-cloak:保持在元素上直到关联实例结束编译。可以用来隐藏未编译的Mustache标签直到实例准备完毕。
<div id="app" v-cloak>
<p>{{ message }}</p>
</div>
<style>
[v-cloak] {
display: none;
}
</style>
<script>
new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
父子组件通信 Props:父组件通过props向子组件传递数据。 emit: 子组件通过$emit向父组件发送事件。
兄弟组件通信
事件总线:通过一个空的Vue实例作为事件总线,实现兄弟组件之间的通信。
<!-- 事件总线 -->
<div id="app">
<component-a></component-a>
<component-b></component-b>
</div>
<script>
var eventBus = new Vue();
Vue.component('component-a', {
template: `
<div>
<p>Component A</p>
<button @click="sendMessage">Send Message</button>
</div>
`,
methods: {
sendMessage: function () {
eventBus.$emit('message-from-a', 'Hello from Component A');
}
}
});
Vue.component('component-b', {
template: `
<div>
<p>Component B</p>
<p>{{ message }}</p>
</div>
`,
data: function () {
return {
message: ''
};
},
created: function () {
eventBus.$on('message-from-a', (message) => {
this.message = message;
});
}
});
new Vue({
el: '#app'
});
</script>
<!-- 跨级组件通信 -->
<div id="app">
<ancestor-component></ancestor-component>
</div>
<script>
Vue.component('ancestor-component', {
template: `
<div>
<p>Ancestor Component</p>
<parent-component></parent-component>
</div>
`,
provide: function () {
return {
message: 'Hello from Ancestor'
};
}
});
Vue.component('parent-component', {
template: `
<div>
<p>Parent Component</p>
<child-component></child-component>
</div>
`
});
Vue.component('child-component', {
template: `
<div>
<p>Child Component</p>
<p>{{ message }}</p>
</div>
`,
inject: ['message']
});
new Vue({
el: '#app'
});
</script>
State:存储应用的状态数据。 状态是响应式的,当状态发生变化时,依赖该状态的组件会自动更新。 状态是全局的,可以在任何组件中访问。
Getters:从状态中派生出一些状态(类似于计算属性)。 可以对 state 进行计算或过滤。 结果会被缓存,只有当依赖的状态发生变化时才会重新计算。
Mutations:唯一修改状态的地方,必须是同步函数。 每个 mutation 都有一个字符串类型的事件类型(type)和一个回调函数(handler)。 通过 commit 方法触发 mutation。
Actions:用于提交 mutations,可以包含异步操作。 通过 dispatch 方法触发 action。 可以在 action 中执行异步操作(如 API 请求),然后提交 mutation 来修改 state。
Modules:将 store 分割成模块,每个模块拥有自己的 state、getters、mutations 和 actions。 适用于大型项目,可以将状态管理逻辑拆分到不同的模块中。 模块可以嵌套。
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++;
}
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
}
});
export default store;
import Vue from 'vue';
import store from './store';
new Vue({
store,
el: '#app',
computed: {
count() {
return this.$store.state.count;
},
doubleCount() {
return this.$store.getters.doubleCount;
}
},
methods: {
increment() {
this.$store.commit('increment');
},
incrementAsync() {
this.$store.dispatch('incrementAsync');
}
}
});
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
export default {
computed: {
count() {
return this.$store.state.count;
},
doubleCount() {
return this.$store.getters.doubleCount;
}
},
methods: {
increment() {
this.$store.commit('increment');
},
incrementAsync() {
this.$store.dispatch('incrementAsync');
}
}
};
</script>
State:存储应用的状态数据。 Getters:从状态中派生出一些状态(类似于计算属性)。 Actions:用于修改状态,可以是同步或异步的。 Store:每个 store 都是一个独立的模块,不需要像 Vuex 那样分模块。
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++;
},
incrementAsync() {
setTimeout(() => {
this.increment();
}, 1000);
}
},
getters: {
doubleCount(state) {
return state.count * 2;
}
}
});
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
import { useCounterStore } from './stores/counter';
export default {
setup() {
const counterStore = useCounterStore();
return {
count: counterStore.count,
doubleCount: counterStore.doubleCount,
increment: counterStore.increment,
incrementAsync: counterStore.incrementAsync
};
}
};
</script>
vuex和pinia的区别
Vuex
集中式状态管理:Vuex 采用集中式存储管理应用的所有组件的状态。 严格的流程:通过 state、mutations、actions、getters 等概念来管理状态,流程较为严格。
Pinia
轻量级和模块化:Pinia 的设计更加轻量级和模块化,每个 store 都是一个独立的模块。 更灵活的 API:Pinia 提供了更灵活的 API,去掉了 mutations,直接使用 actions 来修改状态。
TypeScript 支持 Vuex
TypeScript 支持:Vuex 对 TypeScript 的支持需要额外的配置和插件,使用起来相对复杂。
Pinia
TypeScript 支持:Pinia 内置了更好的 TypeScript 支持,提供了更好的类型推断和类型检查。
模块化 Vuex
模块化:Vuex 通过 modules 将 store 分割成模块,每个模块拥有自己的 state、mutations、actions 和 getters。
Pinia
模块化:Pinia 的每个 store 都是一个独立的模块,无需额外的模块化配置。
性能 Vuex
性能:Vuex 的性能在处理大型应用时可能成为瓶颈,尤其是在模块化场景下。
Pinia
性能:Pinia 的设计更加轻量级,性能更好,尤其是在处理大型应用时。
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path: '/',
component: () => import('@/views/Home.vue') // 首页组件
},
{
path: '/about',
component: () => import('@/views/About.vue') // 关于页面组件
}
];
const router = new VueRouter({
mode: 'history', // 使用 history 模式(去掉 URL 中的 #)
routes
});
export default router;
<template>
<div>
<router-link to="/">Home</router-link>
<router-link to="/about">About</router-link>
<router-view></router-view> <!-- 路由匹配的组件会渲染在这里 -->
</div>
</template>
配置动态路由
const routes = [
{
path: '/user/:id', // 动态路由参数
component: () => import('@/views/User.vue')
}
];
在组件中获取动态参数
<template>
<div>
<p>User ID: {{ userId }}</p>
</div>
</template>
<script>
export default {
computed: {
userId() {
return this.$route.params.id; // 获取动态路由参数
}
}
};
</script>
配置嵌套路由
const routes = [
{
path: '/user/:id',
component: () => import('@/views/User.vue'),
children: [
{
path: 'profile', // 子路由路径
component: () => import('@/views/UserProfile.vue')
},
{
path: 'posts', // 子路由路径
component: () => import('@/views/UserPosts.vue')
}
]
}
];
在父组件中使用
<template>
<div>
<h2>User Page</h2>
<router-link to="/user/1/profile">Profile</router-link>
<router-link to="/user/1/posts">Posts</router-link>
<router-view></router-view> <!-- 子路由组件会渲染在这里 -->
</div>
</template>
全局前置守卫 (beforeEach)
router.beforeEach((to, from, next) => {
console.log('Navigating from', from.path, 'to', to.path);
if (to.path === '/restricted') {
next('/login'); // 重定向到登录页
} else {
next(); // 继续导航
}
});
全局后置钩子 (afterEach)
router.afterEach((to, from) => {
console.log('Navigation complete:', to.path);
});
路由独享守卫 (beforeEnter)
const routes = [
{
path: '/admin',
component: () => import('@/views/Admin.vue'),
beforeEnter: (to, from, next) => {
if (isAdmin()) {
next();
} else {
next('/login');
}
}
}
];
组件内守卫
beforeRouteEnter:进入路由前调用。 beforeRouteUpdate:路由更新时调用。 beforeRouteLeave:离开路由前调用。
export default {
beforeRouteEnter(to, from, next) {
console.log('Entering route:', to.path);
next();
},
beforeRouteUpdate(to, from, next) {
console.log('Updating route:', to.path);
next();
},
beforeRouteLeave(to, from, next) {
console.log('Leaving route:', from.path);
next();
}
};
导航守卫
全局前置守卫 (beforeEach):用户身份验证、权限控制、页面标题设置、数据预加载、动态路由加载 全局后置钩子 (afterEach):页面访问统计 beforeRouteLeave:防止用户误操作(在用户离开当前页面时,提示用户保存未提交的数据)
在路由跳转时,控制页面的滚动位置 路由切换动画 路由缓存
路由元信息 (meta) 可以用于存储一些额外的信息,例如页面标题、权限等。
配置路由元信息
const routes = [
{
path: '/dashboard',
component: () => import('@/views/Dashboard.vue'),
meta: { requiresAuth: true } // 需要登录
}
];
在导航守卫中使用元信息
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login');
} else {
next();
}
});
路由懒加载可以优化应用的性能,只有在访问路由时才加载对应的组件。
const routes = [
{
path: '/about',
component: () => import('@/views/About.vue') // 懒加载
}
];
除了使用
,还可以通过 JavaScript 进行路由跳转。
this.$router.push('/about');
this.$router.replace('/about');
this.$router.go(-1); // 后退一步
this.$router.go(1); // 前进一步
npm install -g @vue/cli
# 检查 Vue CLI 的版本
vue --version
vue create my-project
项目配置选项 默认配置:使用 Vue 3 或 Vue 2 的默认配置。 手动选择特性:自定义项目配置,例如: Babel TypeScript Router Vuex CSS 预处理器(如 Sass、Less) Linter / Formatter(如 ESLint、Prettier) 单元测试(如 Jest) E2E 测试(如 Cypress)
cd my-project
npm run serve
my-project/
├── node_modules/ # 依赖模块
├── public/ # 静态资源(不会被 webpack 处理)
│ └── index.html # 入口 HTML 文件
├── src/ # 项目源代码
│ ├── assets/ # 静态资源(会被 webpack 处理)
│ ├── components/ # 组件
│ ├── views/ # 路由视图组件
│ ├── router/ # 路由配置
│ ├── store/ # Vuex 状态管理
│ ├── App.vue # 根组件
│ └── main.ts # 入口文件
├── .eslintrc.js # ESLint 配置
├── .gitignore # Git 忽略文件
├── babel.config.js # Babel 配置
├── package.json # 项目依赖和脚本
├── tsconfig.json # TypeScript 配置
└── vue.config.js # Vue 项目配置
全局配置 (vue.config.js)
module.exports = {
publicPath: '/my-app/', // 部署路径
devServer: {
port: 8081, // 开发服务器端口
proxy: 'http://localhost:4000' // 代理 API 请求
},
css: {
loaderOptions: {
sass: {
additionalData: `@import "@/styles/variables.scss";` // 全局引入 SCSS 变量
}
}
}
};
环境变量 .env:默认环境变量。 .env.development:开发环境变量。 .env.production:生产环境变量。
vue add plugin-name
常用插件: Vue Router/Vuex/TypeScript/ESLint
npm run build
Vue 提供了 Vue.directive 方法来注册全局指令,也可以在组件中定义局部指令。
// 全局自定义指令
import Vue from 'vue';
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当绑定元素插入到 DOM 中时调用
inserted(el) {
el.focus(); // 聚焦元素
}
});
// 局部自定义指令
export default {
directives: {
focus: {
inserted(el) {
el.focus();
}
}
}
};
指令钩子函数
bind:指令第一次绑定到元素时调用。 inserted:绑定元素插入父节点时调用。 update:组件更新时调用(但可能在其子组件更新之前)。 componentUpdated:组件及其子组件全部更新后调用。 unbind:指令与元素解绑时调用。
Vue.directive('demo', {
bind(el, binding, vnode) {
console.log('bind');
},
inserted(el, binding, vnode) {
console.log('inserted');
},
update(el, binding, vnode, oldVnode) {
console.log('update');
},
componentUpdated(el, binding, vnode, oldVnode) {
console.log('componentUpdated');
},
unbind(el, binding, vnode) {
console.log('unbind');
}
});
插件可以添加全局方法、指令、过滤器、混入(mixin)等。
编写插件
一个插件通常是一个对象或函数,需要暴露一个 install 方法。
const MyPlugin = {
install(Vue, options) {
// 添加全局方法或属性
Vue.myGlobalMethod = function () {
console.log('MyPlugin: Global method called');
};
// 添加全局指令
Vue.directive('my-directive', {
bind(el, binding, vnode) {
el.style.color = binding.value;
}
});
// 添加全局混入
Vue.mixin({
created() {
console.log('MyPlugin: Mixin created hook');
}
});
// 添加实例方法
Vue.prototype.$myMethod = function (methodOptions) {
console.log('MyPlugin: Instance method called');
};
}
};
使用插件
import Vue from 'vue';
import MyPlugin from './MyPlugin';
Vue.use(MyPlugin, { someOption: true });
插件示例:Toast 插件
const ToastPlugin = {
install(Vue, options) {
Vue.prototype.$toast = function (message) {
const toast = document.createElement('div');
toast.className = 'toast';
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
document.body.removeChild(toast);
}, 3000);
};
}
};
export default ToastPlugin;
在项目中使用
import Vue from 'vue';
import ToastPlugin from './ToastPlugin';
Vue.use(ToastPlugin);
new Vue({
el: '#app',
mounted() {
this.$toast('Hello, this is a toast message!');
}
});
自定义指令的应用场景
DOM 操作:例如自动聚焦输入框、拖拽元素、无限滚动等。 事件监听:例如监听窗口滚动、鼠标点击等。 样式操作:例如动态修改元素的样式。
插件的应用场景
全局功能扩展:例如添加全局方法、指令、过滤器等。 第三方库集成:例如集成 Axios、Lodash 等库。 通用逻辑封装:例如 Toast 提示、模态框、加载动画等。
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
);
export default {
components: {
AsyncComponent
}
};
带加载和错误状态的异步组件
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent({
loader: () => import('./components/MyComponent.vue'),
loadingComponent: LoadingComponent, // 加载时显示的组件
errorComponent: ErrorComponent, // 加载失败时显示的组件
delay: 200, // 延迟显示加载组件的时间(毫秒)
timeout: 3000 // 加载超时时间(毫秒)
});
export default {
components: {
AsyncComponent
}
};
使用场景
大型组件:将大型组件拆分为异步组件,减少初始加载时间。 条件渲染的组件:在需要时才加载组件,例如模态框、选项卡内容等。
路由懒加载是指将路由对应的组件按需加载,只有在访问该路由时才会加载对应的组件。这种方式可以减少初始加载的 JavaScript 文件大小。
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import('@/views/Home.vue') // 懒加载
},
{
path: '/about',
component: () => import('@/views/About.vue') // 懒加载
}
]
});
export default router;
带加载和错误状态的路由懒加载
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => ({
component: import('@/views/Home.vue'),
loading: LoadingComponent, // 加载时显示的组件
error: ErrorComponent, // 加载失败时显示的组件
delay: 200, // 延迟显示加载组件的时间(毫秒)
timeout: 3000 // 加载超时时间(毫秒)
})
}
]
});
export default router;
使用场景
大型单页面应用:将不同路由对应的组件拆分为多个文件,减少初始加载时间。 按需加载:在用户访问特定路由时才加载对应的组件。
Webpack 的魔法注释
Webpack 支持使用魔法注释(Magic Comments)来指定代码分割的文件名。
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import(/* webpackChunkName: "home" */ '@/views/Home.vue')
},
{
path: '/about',
component: () => import(/* webpackChunkName: "about" */ '@/views/About.vue')
}
]
});
预加载和预取
Webpack 还支持预加载(Preload)和预取(Prefetch)功能,可以在浏览器空闲时提前加载资源。
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import(/* webpackPrefetch: true */ '@/views/Home.vue')
},
{
path: '/about',
component: () => import(/* webpackPreload: true */ '@/views/About.vue')
}
]
});
性能优化建议
合理拆分代码:将大型组件和路由拆分为多个文件,按需加载。 使用加载和错误状态:在加载异步组件或路由时,显示加载状态,提升用户体验。 预加载关键资源:使用 Webpack 的预加载功能,提前加载关键资源。 监控性能:使用工具(如 Lighthouse、Webpack Bundle Analyzer)监控和分析应用性能。
Vue 3 引入了 Composition API,这是一种新的组织和复用代码逻辑的方式,旨在解决 Vue 2 中 Options API 的局限性。
Vue 3 在性能方面进行了大量优化,包括更快的渲染速度、更小的包体积和更好的内存管理。 渲染性能: 虚拟 DOM 重写:Vue 3 的虚拟 DOM 实现更加高效,减少了渲染开销。 静态树提升:Vue 3 会提升静态节点,减少重复渲染。
包体积 Tree-shaking:Vue 3 支持 Tree-shaking,未使用的代码不会被打包,减少了包体积。 模块化:Vue 3 的模块化设计使得开发者可以按需引入功能。
内存管理 Proxy 代替 defineProperty:Vue 3 使用 Proxy 实现响应式系统,性能更好,内存占用更少。
Vue 2 生命周期钩子 beforeCreate created beforeMount mounted beforeUpdate updated beforeDestroy destroyed
Vue 3 生命周期钩子 setup:在组件创建之前执行,替代 beforeCreate 和 created。 onBeforeMount onMounted onBeforeUpdate onUpdated onBeforeUnmount onUnmounted
Teleport: 可以将子组件渲染到 DOM 中的任意位置 Fragments: Vue 3 支持多根节点组件,不再需要包裹一个根元素。 Suspense: 用于处理异步组件的加载状态。
优缺点: Composition API:更好的逻辑复用、更灵活的组织代码方式。
实现原理
劫持属性:遍历对象的每个属性,使用 Object.defineProperty 将其转换为 getter 和 setter。 依赖收集:在 getter 中收集依赖(Watcher),在 setter 中通知依赖更新。
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log(`get ${key}: ${val}`);
return val;
},
set(newVal) {
if (newVal !== val) {
console.log(`set ${key}: ${newVal}`);
val = newVal;
// 触发更新
}
}
});
}
const data = {};
defineReactive(data, 'message', 'Hello Vue');
console.log(data.message); // get message: Hello Vue
data.message = 'Hi Vue'; // set message: Hi Vue
局限性 无法检测新增或删除的属性:Object.defineProperty 只能劫持已经存在的属性,无法检测到对象属性的新增或删除。 数组的局限性:Vue 2 通过重写数组的变异方法(如 push、pop 等)来实现数组的响应式,但无法检测到直接通过索引修改数组元素或修改数组长度。
实现原理
创建代理对象:使用 Proxy 创建一个代理对象,拦截对目标对象的操作。 依赖收集:在 get 拦截器中收集依赖,在 set 拦截器中通知依赖更新。
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log(`get ${key}: ${target[key]}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
if (target[key] !== value) {
console.log(`set ${key}: ${value}`);
Reflect.set(target, key, value, receiver);
// 触发更新
}
return true;
}
});
}
const data = reactive({ message: 'Hello Vue' });
console.log(data.message); // get message: Hello Vue
data.message = 'Hi Vue'; // set message: Hi Vue
虚拟 DOM 的基本概念 虚拟 DOM 是一个轻量级的 JavaScript 对象,它是对真实 DOM 的抽象表示。虚拟 DOM 的主要作用是: 减少直接操作真实 DOM 的开销:直接操作 DOM 是非常昂贵的,虚拟 DOM 通过批量更新和最小化 DOM 操作来提升性能。 跨平台渲染:虚拟 DOM 可以渲染到不同的平台(如浏览器、移动端、服务器端)。
Diff 算法 Diff 算法是虚拟 DOM 的核心,用于比较新旧虚拟 DOM 树的差异。Vue 和 React 的 Diff 算法有一些差异,但核心思想相似。
Diff 算法的基本原则 同级比较:只比较同一层级的节点,不跨层级比较。 Key 的作用:通过 key 属性标识节点,优化列表渲染的性能。 最小化操作:尽量复用节点,减少 DOM 操作。
Diff 算法的步骤 比较根节点: 如果节点类型不同,直接替换整个节点。 如果节点类型相同,比较节点的属性和子节点。
比较子节点: 使用双指针算法(React)或递归算法(Vue)比较子节点的差异。 通过 key 优化列表节点的比较。
Diff 算法的优化策略 为了提高 Diff 算法的性能,Vue 和 React 都采用了一些优化策略。
Key 的作用 唯一标识:通过 key 唯一标识节点,确保节点的复用和正确更新。 优化列表渲染:在列表渲染时,使用 key 可以显著提升性能。
同级比较 减少比较范围:只比较同一层级的节点,不跨层级比较,减少比较的复杂度。
双端比较 优化列表更新:使用双端比较算法(头尾指针)优化列表的更新,减少 DOM 操作。
批量更新 减少 DOM 操作:将多次 DOM 操作合并为一次,减少浏览器的重绘和回流。
SSR 的工作流程
服务器端渲染: 服务器接收到客户端请求后,根据路由匹配对应的 Vue 组件。 在服务器端将 Vue 组件渲染成 HTML 字符串。 将 HTML 字符串发送到客户端。
客户端激活: 客户端接收到 HTML 后,直接显示内容。 Vue 在客户端接管 HTML,将其转换为可交互的应用。
SSR 的优势 更快的首屏加载:客户端直接接收到渲染好的 HTML,无需等待 JavaScript 加载和执行。 更好的 SEO:搜索引擎可以直接抓取服务器端渲染的 HTML,提升 SEO 效果。 更好的用户体验:用户可以看到内容的时间更早,减少白屏时间。
基本实现步骤: 创建 Vue 实例:在服务器端创建 Vue 实例。 渲染 HTML:使用 vue-server-renderer 将 Vue 实例渲染成 HTML 字符串。 发送 HTML:将渲染好的 HTML 字符串发送到客户端。
const Vue = require('vue');
const renderer = require('vue-server-renderer').createRenderer();
const app = new Vue({
template: '<div>Hello, SSR!</div>'
});
renderer.renderToString(app, (err, html) => {
if (err) throw err;
console.log(html); // 输出渲染好的 HTML 字符串
});
SSR 适用于以下场景:
SEO 优化 搜索引擎抓取:搜索引擎可以直接抓取服务器端渲染的 HTML,提升 SEO 效果。 动态内容:对于动态内容(如博客、新闻网站),SSR 可以确保搜索引擎能够正确抓取和索引。
首屏加载性能 快速显示内容:SSR 可以在服务器端渲染好 HTML,客户端直接显示内容,减少白屏时间。 低性能设备:对于低性能设备(如移动端),SSR 可以显著提升用户体验。
社交分享 动态 meta 标签:SSR 可以在服务器端动态生成 meta 标签,确保社交分享时显示正确的内容。
高并发场景 缓存 HTML:对于高并发场景,可以将渲染好的 HTML 缓存起来,减少服务器压力。
Vue SSR 的挑战 虽然 SSR 有很多优势,但也带来了一些挑战:
服务器压力 高并发:SSR 需要在服务器端渲染 HTML,会增加服务器的压力。
缓存策略:需要合理设计缓存策略,减少服务器压力。
状态管理:需要在服务器端和客户端之间同步状态,增加了状态管理的复杂度。
客户端激活 hydration:客户端需要将服务器端渲染的 HTML 转换为可交互的应用,这个过程称为 hydration。如果 HTML 和客户端的状态不一致,可能会导致问题。
Vue SSR 的优化策略 为了提升 SSR 的性能和开发体验,可以采用以下优化策略:
缓存 HTML 页面缓存:将渲染好的 HTML 缓存起来,减少服务器压力。
组件缓存:将常用的组件缓存起来,提升渲染性能。
异步组件:使用异步组件,按需加载组件,提升性能。
预取数据 数据预取:在服务器端预取数据,确保客户端接收到完整的 HTML。
使用 CDN 静态资源加速:将静态资源(如 JavaScript、CSS、图片)放在 CDN 上,加速资源加载。
使用 Proxy 实现双向绑定的核心原理与 Object.defineProperty 类似,但 Proxy 更加灵活和强大:
数据劫持:使用 Proxy 拦截对象的操作。 依赖收集:在 get 拦截器中收集依赖(Watcher),在 set 拦截器中通知依赖更新。 双向绑定:将输入框的值与数据绑定,实现双向同步。
Vuex 的核心原理包括:
状态管理:通过一个全局的 state 对象存储应用的状态。 响应式系统:使用 Vue 的响应式系统使 state 变为响应式。 提交 mutations:通过 commit 方法提交 mutation 来修改 state。 分发 actions:通过 dispatch 方法分发 action,action 可以包含异步操作。 Getter:通过 getter 从 state 中派生出一些状态。
Vue Router 的核心原理包括:
路由映射:将 URL 路径映射到对应的组件。 监听 URL 变化:通过 hashchange 或 popstate 事件监听 URL 的变化。 动态渲染组件:根据当前 URL 动态渲染对应的组件。
按需加载:只有在请求时才会加载所需的模块,减少了初始加载时间。
1.2 高效的热更新 基于 ES 模块的热更新:Vite 通过原生 ES 模块实现高效的热更新,只更新修改的模块,而不是重新打包整个应用。
1.3 支持多种前端框架 Vue:Vite 对 Vue 提供了开箱即用的支持。
React:Vite 也支持 React 和其他前端框架。
1.4 内置构建工具 生产环境构建:Vite 使用 Rollup 进行生产环境的构建,生成优化的静态文件。
按需加载:只有在请求时才会加载所需的模块,减少了初始加载时间。
2.2 更快的热更新 模块级热更新:Vite 只更新修改的模块,而不是重新打包整个应用,热更新速度更快。
2.3 更好的开发体验 开箱即用的支持:Vite 对 Vue、React 等前端框架提供了开箱即用的支持。
丰富的插件生态:Vite 拥有丰富的插件生态,可以扩展其功能。
常用插件 @vitejs/plugin-vue:支持 Vue 单文件组件。
@vitejs/plugin-react:支持 React。
vite-plugin-pwa:支持 PWA。
vite-plugin-svg-icons:支持 SVG 图标。
开发优先:Vite 专注于提升开发体验,提供更快的启动速度和热更新。
Webpack 基于打包:Webpack 将所有模块打包成一个或多个文件,适用于生产环境。
功能全面:Webpack 提供了丰富的功能和插件,适用于复杂的项目。
使用原生 ES 模块,按需加载模块。
启动时只需启动开发服务器,无需打包。
生产模式:
使用 Rollup 进行生产环境的构建,生成优化的静态文件。
Webpack 开发模式:
将所有模块打包成一个或多个文件。
启动时需要打包整个应用,启动速度较慢。
生产模式:
使用 Webpack 进行生产环境的构建,生成优化的静态文件。
按需加载:只有在请求时才会加载所需的模块,减少了初始加载时间。
Webpack 较慢启动:在开发模式下,Webpack 需要打包整个应用,启动速度较慢。
全量加载:需要加载整个应用的代码,初始加载时间较长。
Webpack 较慢热更新:Webpack 需要重新打包修改的模块,热更新速度较慢。
Webpack 使用 Webpack:Webpack 使用自身的构建工具进行生产环境的构建,生成优化的静态文件。
开发体验优先:Vite 专注于提升开发体验,适合快速迭代的项目。
Webpack 适合复杂项目:Webpack 适合复杂的项目,尤其是需要大量自定义配置的项目。
功能全面:Webpack 提供了丰富的功能和插件,适合需要高度定制的项目。
Webpack 配置文件:Webpack 的配置文件是 webpack.config.js,配置相对复杂。
只渲染可见区域的内容:通过计算可见区域的范围,只渲染当前可见的列表项。
动态调整内容的位置:通过 CSS 的 transform 或 position 属性动态调整列表项的位置,模拟滚动效果。
渲染可见的列表项:只渲染当前可见的列表项,减少 DOM 操作。
动态调整内容的位置:通过 CSS 动态调整列表项的位置,模拟滚动效果。
节流滚动事件:使用节流函数(如 lodash.throttle)减少滚动事件的触发频率。
预加载:提前加载可见区域外的部分列表项,减少滚动时的加载延迟。
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
1.2 缓存路由组件
<keep-alive>
<router-view></router-view>
</keep-alive>
activated:组件被激活时调用(从缓存中恢复)。
deactivated:组件被停用时调用(进入缓存)。
缓存特定组件
缓存的最大数量
结合 vue-router 使用 在 vue-router 中,可以通过 meta 字段控制哪些路由组件需要缓存。
手动控制缓存 在某些情况下,你可能需要手动控制组件的缓存。可以通过 $refs 访问组件实例,并调用 $forceUpdate 强制更新。
Vue 会尝试复用已有的 DOM 元素,而不是重新创建新的 DOM 元素。 删除 4 后,Vue 会更新 DOM 中对应位置的元素内容,而不是移动或删除其他元素。
行为:
删除 4 后,列表变为 [1, 2, 3, 5, 6]。 Vue 会更新 DOM 中第 4 个
的内容,从 4 变为 5。 Vue 会更新 DOM 中第 5 个 的内容,从 5 变为 6。 最后一个 (原本是 6)会被移除。 其他 元素的内容不会变化,但它们的文本内容可能会被重新赋值。
Vue 会精确地删除对应的 DOM 元素。 其他元素不会受到影响,DOM 操作更加高效。
行为:
删除 4 后,列表变为 [1, 2, 3, 5, 6]。 Vue 会直接删除 DOM 中 key 为 4 的
元素。 其他 元素不会发生变化,DOM 操作更加高效。
页面不会自动重新渲染
原因: Vue 的响应式系统通过 Object.defineProperty 或 Proxy 监听对象属性的变化。如果你直接给 a.b 赋值,Vue 无法感知到这种变化,因此不会触发视图更新。
解决方法 要让 Vue 检测到 a.b 的变化并重新渲染页面,可以使用以下方法:
Vue.set(a, 'b', newValue);
// Vue3
import { set } from 'vue';
set(a, 'b', newValue);
a = { ...a, b: newValue };
Vue.set(this, 'a', { b: newValue });