从 vue2到 vue3学习总结

104 阅读8分钟

学习vue3

  1. 选项式api
  2. 更轻量化了(都需要引入)
  3. 下载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:... })实现响应式

如何选择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处理

    1. 该组件实例对应的整个 DOM 树会被从真实的文档流 (DOM tree) 中完全移除 (detached) 。这就是为什么在页面检查器里看不到它的 DOM 了。
    2. 当这个组件再次被激活时,keep-alive 会从缓存中找到这个实例,直接复用这个组件实例(保留所有状态),并将它对应的 DOM 树重新插入 (attached) 到文档流中
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.注意事项
  1. 组件必须设置name选项:否则include/exclude无法匹配
  2. 避免内存泄漏:及时清理不需要缓存的组件(如通过max或动态include)
  3. SSR不兼容:keep-alive仅在客户端渲染中生效
  4. 缓存组件的状态保留:表单内容等会被保留,需手动重置或通过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.注意事项
  1. 组件命名:异步组件需显式申明name选项,以便调试和keep-alive匹配
  2. SSR限制:异步组件在服务端渲染中需特殊处理(如占位内容)
  3. 错误边界:结合onErrorCaptured全局捕获异步组件错误
  4. 过度分割:避免过多小模块导致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.应用场景
  1. 大型应用模块懒加载:如管理后台的复杂表单/图表组件
  2. 条件渲染组件:用户交互后才加载的非必要组件(如弹窗)
  3. 路由级懒加载:结合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.注意事项
  1. this.$router的兼容性:选项式API中仍可用,组合式API推荐useRouter
  2. SSR适配:需使用createMemoryHistory并处理客户端激活
  3. 路由命名冲突:动态路由添加时注意避免重复路径或名称
  4. 导航守卫异步处理:确保调用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

  • 选项式APIthis.$store.state.countmapState/mapGetters辅助函数
  • 组合式APIconst 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.注意事项
  1. 避免直接修改State:必须通过commitdispatch触发变更。
  2. 模块复用:动态注册模块时需要注意生命周期管理(如路由切换时卸载)
  3. 性能优化:避免在Getters中执行高开销计算,使用缓存或拆分逻辑
  4. TypeScript支持:Vuex4对TS支持较弱,推荐使用pinia替代
2、Pinia
1.Pinia核心概念与优势

1. Pinia是什么?

  • Vue官方推荐的新一代状态管理库,替代Vuex,专为Vue3设计,全面支持Composition APITypeScript
  • 核心特点:简洁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.注意事项
  1. 避免直接修改Store实例:使用Actions或$patch确保状态变更
  2. 性能优化:拆分高频变更状态到独立Store,减少渲染影响
  3. 合理设计Store:按业务功能划分Store,避免单一Store过于臃肿
  4. 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对比
特性SPAMPA (多页面应用)​
页面数量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过期自动跳转登录页