学习vue3
- 选项式api
- 更轻量化了(都需要引入)
- 下载vue3项目 npm create vue@latest
1. 生命周期
vue3生命周期
初始化阶段
setup() 替代beforeCreated和created // 注意:无法访问this
挂载阶段
onBeforeMount() 组件挂载到DOM前调用,此时虚拟DOM已生成但未渲染
onMounted() 组件挂载完成,可操作DOM或发起网络请求
更新阶段
onBeforeUpdate() 数据变化导致DOM更新前触发,适合获取更新前的DOM状态
onUpdated() DOM更新执行后 // 注意:避免在此修改状态,可能导致无限循环
卸载阶段
onBeforeUnmount() 组件卸载前调用,清理定时器,取消网络请求,移除事件监听
onUnMounted() 组件卸载后触发,此时子组件已全部卸载
keepAlive组件缓存的生命周期
onActivated() 缓存组件激活
onDeactivated() 停用时调用。
vue2->vue3的生命周期的区别
1.钩子函数重命名
beforeDestory -> onBeforeUnmount
destoryed -> onUnmounted
2.都加了on
父子组件生命周期执行顺序
3.组件缓存
vue3首次渲染不触发,只在组件缓存状态变化时触发,vue2首次会触发
2.组件通信
defineProps、defineEmits是vue3的一个宏函数,使用时可不导入
Props/Emits(父子通信)
<!-- 父组件 -->
<ChildComponent :message="parentMessage" />
<!-- 子组件 -->
<script setup>
<template>
<div>
<p>{{ message }}</p>
</div>
</template>
const props = defineProps({
message: String
});
// defineProps(["message"])
</script>
emits(子->父)
<!-- 子组件 -->
<button @click="sendToParent">点击触发父组件方法</button>
<script setup>
// 先在顶级作用域声明事件
const emit = defineEmits(['childEvent']);
const sendToParent = () => {
// 然后在方法中使用 emit
emit('childEvent', '来自子组件的数据');
};
</script>
<!-- 父组件 -->
<Child @childEvent="handleUpdate" />
ref/expose(父访问子组件)
<!-- 父组件 -->
<Child ref="childRef" />
<script setup>
// 当它的初始值设为null时,就创建了一个空的引用,之后可以将其绑定到 DOM 元素或者组件实例上
const childRef = ref(null);
// 访问子组件暴露的属性/方法
childRef.value.childMethod();
</script>
<!-- 子组件 -->
const childMethod = () => {};
// 暴露方法给父组件
defineExpose({ childMethod })
</script>
provide/inject(跨层级通信)
只能父给子孙传递,单向
// 父级
const count = ref(0);
provide('count', count);
// 子组件
const count = inject('count');
// 当父级修改 count.value 时,子组件会自动更新
子组件不应直接修改注入的数据,而应通过父级提供的方法间接修改
// 父级
const count = ref(0);
const increment = () => { count.value++; };
provide('increment', increment);
// 子组件
const increment = inject('increment');
// 调用父级方法修改数据
increment();
事件总线(Event Bus)
在 Vue3 中,官方不再推荐使用内置的事件总线(Vue2 中的this.$bus),而是建议使用第三方库(如mitt)或自定义实现。
3. ref 与 reactive 区别
为什么ref需要.value?
-
设计目的:统一处理基本类型和对象类型。
- 基本类型无法通过Proxy代理,ref通过封装对象(
{ value:... })实现响应式
- 基本类型无法通过Proxy代理,ref通过封装对象(
如何选择ref和reactive?
-
优先ref
- 管理基本类型数据
- 需要明确的数据引用(如传递到函数中仍保持响应性)
-
优先reactive
- 管理复杂对象/数组,避免频繁使用.value
- 需要深层嵌套的响应式数据
如何解构reactive对象且保持响应性?
- 使用toRefs
const state = reactive({ count: 0, name: 'Vue' });
const { count, name } = toRefs(state); // 保持响应性
count.value++; // 生效
reactive的局限性是什么?
- 无法直接替换整个对象
- 解决方案:使用Object.assign或ref包裹对象
let obj = reactive({ a: 1 }); obj = { a: 2 }; // 响应式丢失!
Object.assign(obj, { a: 2 }); // 保持响应性
const objRef = ref({ a: 1 }); // 替换整个对象时响应式有效
原理深入
ref的响应式实现
- 基本类型:通过
RefImpl类实现,而RefImpl类内部使用了 JavaScript 原生的 getter/setter 语法(不是直接调用Object.defineProperty)。 - 对象类型:内部转化为reactive代理
reactive的响应式实现
- 基于Proxy代理整个对象,递归处理嵌套熟悉
- 依赖收集:在get时调用track收集依赖
- 触发更新:在set时调用trigger通知更新
属性透传($attr)扩展
<!-- 父组件 -->
<Child class="child-style" data-id="123" />
<!-- 子组件 -->
<div v-bind="$attrs"></div>
<script setup>
// 禁用自动继承
defineOptions({ inheritAttrs: false });
</script>
keep-alive
1.核心作用和使用场景
1.作用
- 缓存组件实例:避免重复销毁和创建,保留组件状态(如
DOM结构、响应式数据、事件监听) - 提升性能:适用于需要频繁切换但状态需保留的组件(如
Tab页、表单填写页)
2.使用方式
<template>
<keep-alive :include="['ComponentA', 'ComponentB']" :max="5">
<component :is="currentComponent"></component>
</keep-alive>
</template>
2.生命周期钩子变化
-
新增钩子(仅在被缓存的组件中触发)
- onActivated:组件被激活(插入DOM)时触发。
- onDeactivated:组件被停用(移除DOM)时触发
-
执行顺序:
- 首次加载:
onCreate->onMounted->onActivated - 切换离开:
onDeactivated - 再次进入:
onActivated - 彻底销毁:
onUnmounted
- 首次加载:
3.关键配置属性
1.include
- 匹配组件名称(name选项),仅缓存匹配的组件
- 支持字符串、正则、数组
<!-- 缓存以 "Test" 开头的组件 -->
<keep-alive :include="/^Test/">
2.exclude
- 排除指定组件,优先级高于include
3.max
- 最大缓存实例数,超出时按LRU(最近最少使用)策略淘汰旧实例
- LUR原理:有限淘汰最久未访问的实例
4.高频面试题
1.keep-alive实现原理
-
缓存机制:通过Map或Object缓存组件vnode实例,渲染时直接从缓存中取
-
DOM处理:
- 该组件实例对应的整个 DOM 树会被从真实的文档流 (DOM tree) 中完全移除 (
detached) 。这就是为什么在页面检查器里看不到它的 DOM 了。 - 当这个组件再次被激活时,
keep-alive会从缓存中找到这个实例,直接复用这个组件实例(保留所有状态),并将它对应的 DOM 树重新插入 (attached) 到文档流中。
- 该组件实例对应的整个 DOM 树会被从真实的文档流 (DOM tree) 中完全移除 (
2.如何动态控制组件缓存
- 方案1:绑定动态include/exclude(响应式变量)
<keep-alive :include="cachedComponents">
- 方案2:通过key强制重新渲染(改变key会销毁旧实例)
<component :is="currentComponent" :key="componentKey">
3.keep-alive如何结合路由使用
- 搭配router-view
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>
- 路由配置:通过meta字段标记需缓存的页面
{ path: '/home', component: Home, meta: { keepAlive: true } }
4.缓存组件如何更新数据
- onActivated中刷新数据
onActivated(() => {
fetchData(); // 重新请求数据
});
5.max属性的作用及淘汰策略
- 作用:避免内存无限增长,限制最大缓存实例
- 淘汰策略:LRU(最近最少使用),优先移除最久未被访问的实例
5.注意事项
- 组件必须设置name选项:否则include/exclude无法匹配
- 避免内存泄漏:及时清理不需要缓存的组件(如通过max或动态include)
- SSR不兼容:keep-alive仅在客户端渲染中生效
- 缓存组件的状态保留:表单内容等会被保留,需手动重置或通过key强制更新
6.实战实例
<template>
<button @click="toggleComponent">切换组件</button>
<keep-alive :include="cachedComponents" :max="3">
<component :is="currentComponent" :key="currentComponent" />
</keep-alive>
</template>
<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
const currentComponent = ref('ComponentA');
const cachedComponents = ref(['ComponentA', 'ComponentB']);
const toggleComponent = () => {
currentComponent.value = currentComponent.value === 'ComponentA' ? 'ComponentB' : 'ComponentA';
};
</script>
异步组件
1.核心概念与使用方式
1.定义异步组件
defineAsyncComponent函数(Vue3推荐方式)
import { defineAsyncComponent } from 'vue';
const AsyncCom = defineAsyncComponent(() => import('./MyComponent.vue'));
- 动态
import()语法(结合构建工具如Webpack/Vite实现代码分割)
const AsyncComp = defineAsyncComponent({
loader: () => import('./MyComponent.vue'),
loadingComponent: loadingSpinner, // 加载中组件
errorComponent: ErrorDisplay, // 错误组件
delay: 1000, // 延迟显示loading(防闪烁)
timeout, // 超时时间
})
2.Suspense组件
- 统一管理异步组件(如异步组件或异步setup函数):
<template>
<Suspense>
<template #default>
<AsyncComp />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
2.高频面试题
1.异步组件的核心作用
- 按需加载:减少初始包体积,提升首屏加载速度
- 性能优化:结合代码分割(
Code Spliting)动态加载非关键组件
2.如何配置异步组件的加载状态和错误处理?
loadingComponent:显示加载中的UI(如loading动画)errorComponent:加载失败时显示错误提示delay:延迟显示loading组件,避免快速加载时闪烁timeout:超时后触发错误组件
3.Suspense和异步组件的关系
- Suspense:内置组件,用于统一管理异步组件的加载状态(如多个异步组件并行加载)
- 异步组件:通过
defineAsyncComponent定义,由Suspense控制占位内容
4.如何实现组件加载失败后的重试逻辑
- 工厂函数返回Promise:在loader中捕获错误并重试
const Async = defineAsyncComponent({
loader: () => import('./MyComponent.vue')
.catch(() => {
// 重试逻辑
return retryImport();
})
})
5.异步组件在路由懒加载中的应用
- Vue Router配置
const router = createRouter({
route: [{
path: '/profile',
component: () => import('./Profile.vue'); // 直接动态导入
// 或使用defineAsyncComponent
component: defineAsyncComponent(() => import('./Profile.vue'))
}]
})
6.Vue3异步组件与Vue2的差异
- 语法差异:Vue3废弃
Vue.component('async-comp',() => import(...)),改用defineAsyncComponent - 功能增强:Vue3支持更细颗粒度的加载状态管理和
Suspense集成
3.底层原理优化
1.代码分割原理
- 构建工具(如webpack)将动态
import()的模块拆分为独立chunk,运行时按需加载
2.异步组件生命周期
- 加载阶段:触发loader -> 下载loader -> 初始化组件
- 缓存机制:已加载的组件实例会被缓存,避免重复加载
3.性能优化策略
- 预加载(prefetch) :通过Webpack魔法注释标记非关键资源
() => import(/* webpackPrefetch: true */ './MyComponent.vue')
- 懒加载阈值:结合路由或用户行为预测延迟加载组件
4.注意事项
- 组件命名:异步组件需显式申明
name选项,以便调试和keep-alive匹配 - SSR限制:异步组件在服务端渲染中需特殊处理(如占位内容)
- 错误边界:结合
onErrorCaptured全局捕获异步组件错误 - 过度分割:避免过多小模块导致HTML请求激增
5.实战代码实例
// 异步组件定义
const AsyncModal = defineAsyncComponent({
lodaer: () => import('./Modal.vue').catch((err) ==> {
console.log('加载失败,3s后重试...');
return new Promise(resolve => {
setTimeout(() => resolve(import('./Modal.vue')), 3000);
})
}),
loadingComponent: LoadingSpainner,
delay: 200,
timeout: 5000
});
// 在组合式API中使用
export default {
setup() {
const showModal = ref(false);
return { showModal, AsyncModal };
}
}
6.应用场景
- 大型应用模块懒加载:如管理后台的复杂表单/图表组件
- 条件渲染组件:用户交互后才加载的非必要组件(如弹窗)
- 路由级懒加载:结合Vue Router提升首屏性能
Vue-Router
1.Vue-Router 4.X核心变化
1.创建路由实例
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(), // 或 createWebHashHistory
routes: [...]
});
2.组合式API支持
- useRouter() :获取路由实例(替代
this.$router) - useRoute() :获取当前路由对象(替代
this.$route)
2.导航守卫
1.全局守卫
router.beforeEach((to, from, next) => { ... })router.afterEach((to, from) => { ... })router.beforeResolve()
2.路由独享守卫
{
path: '/admin',
component: Admin,
beforeEnter: (to, from, next) => { ... }
}
3.组件内守卫
onBeforeRouteUpdate:路由参数变化onBeforeRouteLeave:离开组件前
import { onBeforeRouteLeave } from 'vue-router';
export default {
setup() {
onBeforeRouteLeave((to, from, next) => {
// 清理逻辑
next();
});
}
};
3.高级特性与最佳实践
1.路由懒加载
const User = () => import('./User.vue');
const User = defineAsyncComponent(() => import('./User.vue'));
2.路由元信息(meta)
{ path: '/profile', meta: { requireAuth: true } }
// 在导航守卫中访问:to.meta.requiresAuth
3.动态路由
- 添加路由:
router.addRoute({ path: '/new', component: New }) - 删除路由:
router.removeRoute('route-name')
4.路由组件传参
{ path: '/user/:id', component: User, props: true }
// 组件通过props:['id'] 接收
5.滚动行为控制
const router = createRouter({
scrollBehavior(to, from, savedPosition) {
return savedBehavior || { top: 0 };
}
});
4.高频面试题
1.Vue-Router 4.X 与 3.X 的主要区别
- API命名调整(如
new VueRouter() -> createRouter()) - 组合式API支持(
useRouter/useRoute) - 动态路由API优化(
addRoute/removeRoute)
2.如何实现路由权限控制
- 全局守卫 + 元信息
router.beforeEach((to, from, next) => {
if(to.meta.requireAuth && !isAuthenticated) next('/login');
else next();
});
3.如何处理动态路由加载顺序问题
router.isReady():确保初始路由解析完成再挂载应用
router.isReady().then(() => app.mount('#app'));
4.如何捕获导航错误
router.onError((error) => {
console.error('导航错误', error);
});
5.路由组件如何复用并响应参数变化
onBeforeRouteUpdate:监听路由参数变化
onBeforeRouteUpdate((to, form, next) => {
fetchData(to.params.id);
next();
})
6.实战场景示例
// 动态添加路由(权限控制)
const dynamicRoutes = [
{ path: '/admin', component: Admin, meta: { role: 'admin' } }
];
if (user.role === 'admin') {
dynamicRoutes.forEach(route => router.addRoute(route));
}
// 路由懒加载与预加载(webpack魔法注释)
const Home = () => import( /* webpackPrefetch: true */ './Home.vue' );
7.注意事项
- this.$router的兼容性:选项式API中仍可用,组合式API推荐useRouter
- SSR适配:需使用createMemoryHistory并处理客户端激活
- 路由命名冲突:动态路由添加时注意避免重复路径或名称
- 导航守卫异步处理:确保调用next()或返回Promise
状态管理
1、Vuex
1.Vuex核心概念与工作流程
1.核心角色
State:单一状态树,存储全局数据(响应式)Getter:基于State派生的计算属性(类似组件的computed)Mutation:同步修改State的唯一途径(通过commit触发)Action:处理异步操作,提交Mutations(通过dispatch触发)Modules:模块化拆分复杂Store
2.工作流程
组件 -> dispatch(Action) -> Action -> commit(Mutation) -> Mutation -> 修改State -> 更新视图
3.Vue4.x对Vue3的支持
- 兼容Vue3的
Composition API,但核心API与Vuex 3.x一致 - 通过
useStore替代this.$store(组合式API中)
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
return { store };
}
};
2.核心API与使用
1.定义Store
import { createStore } from 'vuex';
const store = createStore({
state: { count: 0 },
mutation: {
increment(state) { state.count++; }
},
actions: {
asyncIncrement({ commit }) {
setTimeout(() => commit('increment'), 1000);
}
},
getters: {
doubleCount: state => state.count * 2
}
});
2.组件中访问Store
- 选项式API:
this.$store.state.count或mapState/mapGetters辅助函数 - 组合式API:
const store = useStore(); store.state.count;
3.辅助函数
mapState / mapGetters:映射到计算属性mapMutation / MapActions:映射到方法
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapMutations(['increment']),
...mapActions(['asyncIncrement'])
}
};
3.模块化与命名空间
1.模块定义
const moduleA = {
namespaced: true, // 启用命名空间
state: { ... },
mutation: { ... },
action: { ... }
};
const store = createStore({
modules: { a: moduleA }
});
2.命名空间访问
- 直接访问:
store.state.a.moduleData - 辅助函数
...mapActions('a',['moduleAction']),
// 或通过createNamespacedHelpers
const { mapActions } = createNamespacedHelpers('a');
3.模块的局部上下文
- Root State:在模块的Action中通过rootState访问全局状态
- Root Commit:在模块Actions中通过
{ root: true }提交全局Mutation
actions: {
localAction({ commit, dispatch, rootState }) {
commit('localMutation');
dispatch('gloabalAction', null, { root: true });
}
}
4.高级特性与最佳实践
1.严格模式
const store = createStore({ strict: true });
// 直接修改state会抛出错误(仅限开发环境)
2.插件开发
- 订阅Mutations
store.subscribe((mutation, state) => {
console.log('Mutation:', mutation.type);
});
- 持久化插件(如结合localStorage)
const persistPlugin = (store) => {
store.subscribe((mutation, state) => {
localStorage.setItem('vuex-state', JSON.stringify(state));
});
};
3.动态注册模块
store.registerModule('dynamicModule', { ... });
store.unregisterModule('dynamicModule', { ... });
5.高频面试题
1. Vuex与pinia的区别
-
Pinia是Vue官方推荐的新状态管理库,支持Composition API和TypeScript
-
核心差异:
- Pinia无mutations,直接通过actions修改状态(同步/异步均可)
- Pinia基于模块化设计(每个Store独立),无需嵌套模块
- 更简洁的API和TypeScript支持
2. 为什么需要Mutations处理同步,Actions处理异步?
- 调试工具追踪:确保状态变化的同步记录可追踪
- 数据可预测性:避免异步操作导致状态变更顺序混乱
3. Vuex如何实现响应式?
- 底层通过Vue的响应式系统(reactive)实现state的依赖收集和更新触发
4. 如何避免模块命名冲突
- 使用namespaced: true隔离模块,通过命名空间访问状态和方法
5. 大型项目如何优化Vuex使用?
- 按功能拆分为模块,结合动态加载(
registerModule) - 使用
Getter封装复杂状态逻辑
6.实战使用场景
1.模块化与命名空间
// user模块
const userModel = {
namespaced: true,
state: { name: 'Alice' },
mutations:{
setName(state, name) {
state.name = name;
}
}
};
// 组件中调用
methods:{
...mapActions('user', ['setName']);
}
2.状态持久化插件
const persistedState = localStorage.getItem('vuex-state');
const store = createStore({
state: persistedState ? JSON.parse(persistedState) : {}
plugins: [persistPlugin],
});
7.注意事项
- 避免直接修改State:必须通过
commit或dispatch触发变更。 - 模块复用:动态注册模块时需要注意生命周期管理(如路由切换时卸载)
- 性能优化:避免在
Getters中执行高开销计算,使用缓存或拆分逻辑 - TypeScript支持:Vuex4对TS支持较弱,推荐使用
pinia替代
2、Pinia
1.Pinia核心概念与优势
1. Pinia是什么?
- Vue官方推荐的新一代状态管理库,替代Vuex,专为Vue3设计,全面支持
Composition API和TypeScript - 核心特点:简洁API、去除了
Mutations、模块化天然支持、极致TypeScript友好
2. 核心优势(对比Vuex)
- 无Mutations:直接通过
Actions处理同步/异步逻辑 - 扁平化结构:多个
Store代替嵌套模块,更易维护 - TypeScript支持:自动推导类型,无需额外配置
- Devtools集成:支持时间旅行调试和状态快照
- 轻量高效:体积更小,API更简洁
2.核心API与基本使用
1. 定义Store
- Options Store(类似Vue选项式API)
// stores/counters.ts
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++; // 直接修改状态
},
async asyncIncrement() {
setTimeout(() => this.increment(), 1000);
},
},
});
- Setup Store(类似Composition API):
export const useUserStore = defineStore('user', () => {
const name = ref('Alice');
const setName = (newName: string) => { name.value = newName; };
return { name, setName };
})
2. 在组件中使用Store
<script>
import { useCounterStore } from '@/stores/counter';
const counterStore = useCounterStore();
</script>
<template>
<div>{{ counterStore.count }}</div>
<button @click="counterStore.increment()">+1</button>
</template>
3.核心特性
1. State
- 响应式状态:通过
ref或reactive实现,直接修改自动触发更新 - 重置状态:
counterStore.$reset() - 批量更新:
counterStore.$patch({ count: 10 })
2. Getters
- 类似Vue的computed,自动缓存结果
- 支持访问其他Store
getters: {
combinedInfo() {
const userStore = useUserStore();
return `${userStore.name}: ${this.doubleCount}`;
}
}
3. Actions
- 同步/异步均可:无需区分Mutation和Action
- 支持相互调用:通过this访问其他Actions
- 订阅Actions:
const unsubscribe = counterStore.$onAction({ name, after, args }) => {
after(() => console.log(`${name} 执行完成,参数:${args}`));
}
4.模块化组合
1. 模块化设计
- 通过多个Store文件天然实现模块化,无需嵌套结构
- 跨Store调用:
// store/user.ts
import { useCounterStore } from './counter';
export const useUserStore = defineStore('user', {
actions: {
asyncWithCounter() {
const counterStore = useCounterStore();
counterStore.increment();
},
},
});
2. 动态添加Store
- 无需显示注册,按需引入即可(天然支持代码分割)
5.插件与高级用法
1. 插件机制
- 自定义插件(如持久化存储)
const persistPlugin = ({ store }) => {
const savedState = localStorage.getItem(store.$id);
if(savedState){
store.$patch(JSON.parse(savedState));
}
store.$subscribe((mutation, state) => {
localStorage.setItem(store.$id, JSON.stringify(state));
});
};
- 注册插件:
复制代码
import { createPinia } from 'pinia';
const data = createPinia().use(persistPlugin);
2. Devtools支持
- 默认集成Vue DevTools,可追踪状态变化和Actions调用
6.高频面试题
1. 为什么选择Pinia而不是Vuex?
- API简洁:去除了Mutation,减少心智负担
- TypeScript友好:自动类型推导,无需复杂配置
- 模块化更自然:多个Store代替嵌套模块,结构清晰
2. Pinia如何处理异步操作?
- 直接在
Actions中写异步逻辑(如async/await),无需额外步骤
3. Pinia如何实现响应式?
- 底层基于Vue3的
reactive和ref,保证状态变更自动触发更新
4. 如何实现状态持久化?
- 通过插件拦截
$subscribe或$onAction,结合localStorage
5. Pinia如何支持TypeScript?
- Store定义自动推导类型,组件中通过
store.xxx直接获得类型提示
7.实战场景示例
1. 用户认证状态管理
// store/auth.ts
export const useAuthStore = defineStore('auth', {
state: () => ({ token: null, user: null }),
actions: {
async login(username: string, password: string) {
const res = await api.login(usename,password);
this.token = res.token;
this.user = res.user;
},
logout() {
this.$reset();
},
},
});
2. 跨Store组合逻辑
// store/cart.ts
export const useCartStore('cart', {
actions: {
checkout() {
const authStore = useAuthStore();
if(authStore.user) throw new Error('请先登录');
// 调用订单接口...
},
},
});
8.注意事项
- 避免直接修改Store实例:使用Actions或$patch确保状态变更
- 性能优化:拆分高频变更状态到独立Store,减少渲染影响
- 合理设计Store:按业务功能划分Store,避免单一Store过于臃肿
- TypeScript最佳实践:明确标注类型(如
state:() => ({ count: 0 as number }))
vue2与vue3
1.高频面试题
1.为什么Vue3用Proxy替代defineProperty?
- 解决动态属性/数组监听问题
- 初始化性能提升(无需递归遍历)
- 内存占用更低
2.Composition API 解决了什么问题?
- 逻辑复用困难(Mixins的命名冲突/来源不清)
- Options API的逻辑碎片化
- TypeScript类型推导支持弱
3.Vue3的模版编译优化有哪些?
Patch Flags(动态节点标记)- 静态节点提升(减少重复创建)
Block Tree(跳过静态子树对比)
4.Vue3对TypeScript的支持改进
- 源码使用TS重写
Composition API完美支持类型推导defineComponent提供组件类型申明
5.Vue2项目如何升级Vue3?
- 使用
@/vue/compat过渡 - 逐步替换废弃API(
$children,filters等) - 优先迁移新组件,逐步重构旧组件
SPA
1.核心概念
1.定义与特点
-
单页面应用:整个应用只有一个
HTML文件,通过动态替换DOM内容实现"页面"切换 -
核心优势:
- 无刷新跳转(流畅用户体验)
- 前后端分离开发
- 减轻服务器渲染压力
-
主要挑战:
- 首屏加载性能
- SEO优化难度
- 路由管理复杂度
2.SPA与MPA对比
| 特性 | SPA | MPA (多页面应用) |
|---|---|---|
| 页面数量 | 1 个 HTML | 多个 HTML |
| 页面跳转 | 前端路由控制,无刷新 | 整页刷新 |
| 数据请求 | Ajax/Fetch 局部获取数据 | 每次跳转加载完整页面 |
| 开发复杂度 | 高(需前端路由、状态管理等) | 低 |
| SEO 支持 | 差(需额外优化) | 优 |
2.高频面试题
1.SPA首屏加载优化有哪些方案?
- 路由懒加载 + 组件懒加载
- 资源预加载/预取
- CDN加速静态资源
- 开启Gzip/Brotli压缩
- 服务端渲染(SSR)
2.如何解决SPA的SEO问题?
- 预渲染(Prerender)
- 服务端渲染(Nustjs)
- 动态渲染(针对爬虫单独处理)
- 静态站点生成(SSG)
3.Vue Router的导航守卫执行顺序
全局 beforeEach -> 路由 beforeEnter -> 组件 beforeRouteEnter -> 全局 beforeResolve -> 全局 afterEach -> 组件 beforeRouteUpdate
4.如何处理权限路由?
// 动态添加路由
router.beforeEach(async (to) => {
if (!hasAuthInfo) await fetchUserPermissions();
if (to.meta.requiresAdmin && !isAdmin) return '/no-permission';
})
5.SPA如何保持登录状态?
-
JWT存储于localStorage + 刷新Token机制
-
结合HttpOnly Cookie增强安全性
-
Token过期自动跳转登录页