Vue 50问

说说你对SPA单页面的理解,它的优缺点分别是什么?

v-show与 v-if有什么区别?
v-show初始加载所有节点,通过display:none来隐藏和显示节点。
v-if切换时会动态的销毁或者重建节点,具有惰性渲染特性,一开始不占用内存。

Vue的v-for>v-if>v-show优先级 Vue3呢
Vue2 : v-for>v-if>v-show
Vue3:v-if>v-for>v-show
v-for为什么要加key?v-for为什么不能和v-if一起使用?
一、v-for 为什么要加 key?
提升渲染性能

key 作为虚拟 DOM 的唯一标识,帮助 Vue 更高效地复用已有节点,减少不必要的 DOM 操作
使用 key 后,diff 算法能通过 a.key === b.key 快速匹配新旧节点,避免全量比对
避免数据混乱

在动态列表(如排序、增删)中,无 key 可能导致输入类 DOM(如 <input>)状态错乱
key 确保元素在更新时正确追踪,防止就地复用引发的副作用
最佳实践

推荐使用唯一 ID 作为 key,避免 index 或随机值(如 Math.random())
对象类型数据应绑定其唯一属性(如 item.id)
二、v-for 为什么不能和 v-if 一起使用?
优先级问题

v-for 优先级高于 v-if,会导致先遍历整个列表再逐项判断,生成多余 DOM 节点
若列表数据量大且条件复杂,会显著降低性能
性能损耗

每次渲染都会执行 v-if 判断,即使结果不变,造成无意义的计算
逆序操作(如删除首项)可能触发全量 DOM 更新
解决方案

使用 <template> 包裹:先 v-if 判断整体条件,再 v-for 渲染列表
通过计算属性过滤数据:预处理列表,避免模板中混合指

怎样理解 Vue 的单向数据流?
数据流向固定
Vue 的单向数据流规定数据只能从父组件通过 props 流向子组件,子组件无法直接反向修改父组件的数据23。这种设计保证了数据变更路径的唯一性和可追溯性。

修改数据的唯一途径
若子组件需要修改父组件数据,必须通过 $emit 触发自定义事件,由父组件监听事件并更新数据

computed 和watch的区别和运用的场景?
决策维度 优先选择 computed 优先选择 watch
数据关系 多个数据生成一个结果时 单一数据变化触发多个操作时
性能要求 避免重复计算的高频访问场景 低频但需要精确控制响应时
操作类型 纯计算且无副作用时 需执行异步或复杂逻辑时

Vue的过滤器(filters)是如何工作的? Vue3已经移除
通过声明式的数据管道机制,Vue过滤器为模板渲染提供了轻量级的数据格式化解决方案,适用于简单直观的显示层逻辑处理

Vue的nextTick是什么,为什么需要它?
nextTick 是 Vue 中用于处理 DOM 更新异步性的核心 API,其本质是一个异步队列调度器。当响应式数据发生变化时,Vue 会将 DOM 更新操作推入微任务队列,nextTick 的作用就是在这些更新完成后触发回调执行,确保操作基于最新的 DOM 状态

直接给一个数组项赋值,Vue 能检测到变化吗?Vue3呢
Vue不能检测到变化,因为js语言限制无法劫持数组索引
Vue3可以检测到,基于Proxy的响应式系统可深度追踪数组变化

谈谈你对Vue 生命周期的理解?

  1. 创建阶段 (Initialization)
    beforeCreate

触发时机:实例刚被创建,数据观测/事件配置前
可用操作:无法访问 data/methods,适合加载非响应式资源

beforeCreate() {
  console.log(this.message); // undefined
}

created

触发时机:完成数据观测/方法注入,但 DOM 未生成
典型应用:API 数据请求、初始化非 DOM 相关操作

created() {
  axios.get('/api/data').then(res => this.data = res.data);
}
  1. 挂载阶段 (Mounting)
    beforeMount

触发时机:编译模板生成 render 函数,初次 DOM 替换前
特殊用途:SSR 服务端渲染时唯一可同步执行的钩子
mounted

触发时机:DOM 已挂载完成,可访问 this.$el
关键操作:DOM 操作、第三方库初始化(如 ECharts)

mounted() {
  this.chart = echarts.init(this.$refs.chart);
}
  1. 更新阶段 (Updating)
    beforeUpdate

触发时机:数据变化后,虚拟 DOM 重新渲染前
特殊场景:获取更新前的 DOM 状态(如滚动位置保存)
updated

触发时机:虚拟 DOM 重新渲染并应用更新完成
注意事项:避免在此阶段修改数据,可能导致无限循环

  1. 销毁阶段 (Destruction)
    beforeDestroy

触发时机:实例销毁前,组件仍完全可用
关键任务:清除定时器、取消订阅、解绑全局事件

beforeDestroy() {
  clearInterval(this.timer);
  EventBus.$off('custom-event');
}

destroyed

触发时机:实例完全销毁,子组件也被清理
典型场景:性能监控上报(记录组件存活时间)

vue3新的生命周期钩子

组合式api和选项式api的区别

维度 选项式 API (Options API) 组合式 API (Composition API)
代码结构 按功能类型分散到 data、methods 等预设选项中 按业务逻辑聚合代码,通过 setup() 函数统一管理
逻辑拆分 同一功能的代码分散在不同选项中(如数据在 data,方法在 methods) 同一功能的代码集中编写(响应式数据与操作逻辑相邻)
可读性 简单场景直观,复杂场景需跨选项查看逻辑 逻辑关联性强,适合复杂功能的快速定位

特性 选项式 API 组合式 API
复用方式 使用 mixins,易引发命名冲突和来源混淆 通过自定义 Hook 函数(如 useCounter())按需导入
维护成本 高(需处理混入优先级和隐式依赖) 低(函数式封装,依赖显式传递)
灵活性 受限(全局混入影响所有组件) 高(逻辑可组合、嵌套和条件调用)

维度 选项式 API 组合式 API
数据定义 通过 data() 返回对象 使用 ref()/reactive() 显式声明
类型支持 对 TypeScript 支持较弱 天然支持 TypeScript 类型推导
作用域管理 依赖 this 上下文,易产生隐式耦合 无 this 绑定,变量作用域更清晰

Vue 的父组件和子组件生命周期钩子函数执行顺序?
场景 执行顺序
父组件初始化 父 beforeCreate → 父 created → 父 beforeMount → 子组件完整生命周期 → 父 mounted
父组件更新 父 beforeUpdate → 子 beforeUpdate → 子 updated → 父 updated
父组件销毁 父 beforeDestroy → 子 beforeDestroy → 子 destroyed → 父 destroyed
在哪个生命周期内调用异步请求?

image.png

父组件可以监听到子组件的生命周期吗?

  1. 手动事件触发机制
    原理
    子组件生命周期钩子中显式触发自定义事件,父组件通过 @event 监听并响应
<!-- 子组件 -->
<script>
export default {
  mounted() {
    this.$emit('child-mounted') // 触发事件
  }
}
</script>

<!-- 父组件 -->
<ChildComponent @child-mounted="handleChildMounted" />

优点:灵活控制触发逻辑
缺点:需在每个生命周期手动添加 $emit,代码冗余

  1. @hook 语法糖
    原理
    使用 @hook:生命周期名 直接监听子组件原生钩子,无需子组件修改代码
<!-- 父组件监听子组件 mounted -->
<ChildComponent @hook:mounted="handleChildMounted" />

Vue3 调整
钩子名称变化:如 beforeDestroy → beforeUnmount
组合式 API 中仍可用但需注意执行顺序

<!-- Vue3 写法 -->
<ChildComponent @hook:beforeUnmount="handleBeforeUnmount" />

谈谈你对 keep-alive 的了解?

组件中 data 为什么是一个函数?
Vue 组件中 data 必须为函数的核心原因
一、避免数据共享污染
对象引用问题
JavaScript 中对象是引用类型,若直接使用对象形式定义 data,所有组件实例将共享同一内存地址的数据。

// 错误示例:对象形式导致数据共享
data: { count: 0 }  // 所有实例修改 count 会互相影响

函数返回独立对象
通过函数每次返回新对象,确保每个组件实例拥有独立的数据副本。

// 正确示例:函数形式隔离数据
data() { return { count: 0 } }  // 每次实例化生成新对象

v-model的原理?
v-model 本质是语法糖,通过 v-bind 绑定数据 + v-on 监听事件的组合实现双向数据绑定

<!-- 原生输入框等价转换 -->
<input v-model="msg">
<!-- 等价于 -->
<input 
  :value="msg" 
  @input="msg = $event.target.value"
>

Vue 组件间通信有哪几种方式?
一、父子组件通信
Props / $emit

父→子:父组件通过 props 单向传递数据,子组件声明接收

<!-- 父组件 -->
<Child :msg="parentMsg" />

<!-- 子组件(选项式API) -->

props: ['msg']
子→父:子组件通过 $emit 触发自定义事件,父组件监听响应

<!-- 子组件 -->
this.$emit('update', newValue)

<!-- 父组件 -->
<Child @update="handleUpdate" />

ref / $refs
父组件通过 ref 获取子组件实例,直接调用方法或访问数据

<!-- 父组件 -->
<Child ref="childComp" />
<script>
this.$refs.childComp.someMethod()
</script>

Vue3 注意:需用 defineExpose 显式暴露方法
二、跨层级组件通信
事件总线(Event Bus)
创建全局 Vue 实例作为中央事件枢纽,任意组件间触发/监听事件

// 创建事件总线(Vue2)
const bus = new Vue()

// 组件A触发
bus.$emit('custom-event', data)

// 组件B监听
bus.$on('custom-event', handler)

依赖注入(Provide / Inject)
父组件通过 provide 提供数据,后代组件通过 inject 注入使用

<!-- 祖先组件 -->
<script>
export default {
  provide() { return { theme: 'dark' } }
}
</script>

<!-- 后代组件 -->
<script>
export default { inject: ['theme'] }
</script>

响应式技巧:传递函数或响应式对象实现动态更新

attrs /listeners
跨层传递未在 props 中声明的属性和事件(Vue3 合并为 $attrs)

<!-- 中间组件透传 -->
<GrandChild v-bind="$attrs" v-on="$listeners" />

三、复杂场景通信
Vuex 状态管理
集中式状态存储,适用于多组件共享复杂状态场景

// store.js
export default new Vuex.Store({
  state: { count: 0 },
  mutations: { increment: state => state.count++ }
})

// 组件中使用
this.$store.commit('increment')

parent /children
直接访问组件链实例(慎用,易导致强耦合)

// 子组件访问父组件
this.$parent.someMethod()

// 父组件遍历子组件
this.$children.forEach(child => { ... })

你使用过 Vuex 吗?
Vuex 是 Vue.js 的集中式状态管理工具,其核心流程围绕 State → View → Actions → Mutations → State 的闭环设计展开

// store.js
const store = new Vuex.Store({
  state: {
    cartItems: [],
    user: null
  },
  mutations: {
    ADD_TO_CART(state, item) {
      state.cartItems.push(item);
    },
    SET_USER(state, userData) {
      state.user = userData;
    }
  },
  actions: {
    async fetchUser({ commit }, userId) {
      const response = await axios.get(`/api/users/${userId}`);
      commit('SET_USER', response.data);
    }
  },
  getters: {
    cartTotal: state => state.cartItems.reduce((sum, item) => sum + item.price, 0)
  }
});

State(状态存储)

存储应用的全局数据(如用户信息、购物车列表),类似组件的 data。
通过 this.$store.state 或 mapState 访问。
View(视图渲染)

组件从 state 中读取数据并渲染到界面。
Actions(异步操作)

处理异步逻辑(如 API 请求),通过 dispatch 触发。
不直接修改 state,而是提交 mutation。
Mutations(同步修改)

唯一允许直接修改 state 的入口,通过 commit 调用。
必须是同步函数,确保状态变更可追踪。
Getters(派生状态)

类似计算属性,对 state 进行加工后返回新值(如过滤购物车选中商品)

二、Vuex 的核心优势
特性 作用
单一数据源 所有状态集中在 store 中,便于调试和维护
状态响应式 与 Vue 无缝集成,状态变更自动触发组件更新
严格的变更流程 通过 mutations 同步修改状态,actions 处理异步,确保数据流清晰

说说Vuex的工作流程
Vuex 是 Vue.js 的集中式状态管理工具,其核心流程围绕 State → View → Actions → Mutations → State 的闭环设计展开
State(状态存储)

存储应用的全局数据(如用户信息、购物车列表),类似组件的 data。
通过 this.$store.state 或 mapState 访问。
View(视图渲染)

组件从 state 中读取数据并渲染到界面。
Actions(异步操作)

处理异步逻辑(如 API 请求),通过 dispatch 触发。
不直接修改 state,而是提交 mutation。
Mutations(同步修改)

唯一允许直接修改 state 的入口,通过 commit 调用。
必须是同步函数,确保状态变更可追踪。
Getters(派生状态)

类似计算属性,对 state 进行加工后返回新值(如过滤购物车选中商品)

使用过 Vue SSR 吗?说说 SSR?

vue-router 路由模式有几种?

能说下 vue-router 中常用的 hash 和 history 路由模式实现原理吗?

什么是 MVVM?

Vue 是如何实现数据双向绑定的?
依赖追踪体系

Observer:递归遍历数据对象,通过 Object.defineProperty(Vue2)或 Proxy(Vue3)劫持属性访问
Dep:每个属性关联一个依赖收集器,存储所有依赖该属性的 Watcher
Watcher:作为中介触发更新,连接模板编译与数据变更
数据劫持对比

特性 Vue2 (defineProperty) Vue3 (Proxy)
检测能力 无法自动检测新增/删除属性 完美支持动态属性操作
数组处理 需重写数组方法(push/pop等) 原生支持数组变化检测
性能 递归初始化全部属性 惰性代理,按需触发

Vue 框架怎么实现对象和数组的监听?
一、对象的监听
Vue 2(基于 Object.defineProperty)

递归劫持属性:遍历对象每个属性,通过 getter/setter 拦截读写操作。
深层监听:嵌套对象会递归调用 defineProperty 实现深度响应式。
局限性:
无法检测新增/删除属性(需用 Vue.set/Vue.delete)。
初始化时未定义的属性不会触发更新。
示例代码:

const obj = { name: 'Vue' };
Object.defineProperty(obj, 'name', {
  get() { console.log('读取 name'); return this._name; },
  set(val) { console.log('更新 name'); this._name = val; }
});

Vue 3(基于 Proxy)

代理整个对象:直接拦截对象的任意操作(包括新增/删除属性)。
性能优化:惰性递归,仅在访问嵌套对象时触发代理。
示例代码:

const proxy = new Proxy(obj, {
  get(target, key) { console.log('读取', key); return target[key]; },
  set(target, key, val) { console.log('更新', key); target[key] = val; return true; }
});

二、数组的监听
Vue 2 的 hack 实现

重写数组方法:覆盖 push、pop、splice 等 7 个原生方法,在调用时触发更新。
特殊处理:直接通过索引修改(如 arr[0] = 1)无法触发响应式,需用 Vue.set 或 splice。
源码关键逻辑:

const arrayProto = Array.prototype;
const arrayMethods = Object.create(arrayProto);
['push', 'pop', 'splice'].forEach(method => {
  const original = arrayProto[method];
  arrayMethods[method] = function(...args) {
    const result = original.apply(this, args);
    dep.notify(); // 触发依赖更新
    return result;
  };
});

Vue 3 的改进

Proxy 统一代理:直接监听数组索引变化和方法调用,无需特殊处理。
支持 length 修改:通过 Proxy 拦截 length 赋值操作

vue2和vue3响应式原理(Object.defineProperty和Proxy的区别)

Vue2 与 Vue3 响应式原理对比
Vue2 基于 Object.defineProperty,而 Vue3 采用 Proxy 重构响应式系统,两者的核心差异体现在实现机制、性能和功能支持上。以下是详细对比:

一、核心机制对比
特性 Vue2 (Object.defineProperty) Vue3 (Proxy)
实现方式 遍历对象属性,逐个劫持 getter/setter 代理整个对象,拦截任意操作(如 get/set/delete)
属性新增/删除监听 不支持,需 Vue.set/Vue.delete 辅助 原生支持动态属性的增删监听
数组处理 需重写 push、pop 等 7 个方法 直接监听索引修改及 length 变化
嵌套对象处理 初始化时递归遍历所有属性 惰性代理(仅在访问属性时触发代理)
二、实现细节差异
Vue2 的局限性

属性劫持:仅能劫持初始化时存在的属性,无法检测后续新增/删除。
数组缺陷:直接通过索引修改元素(如 arr[0] = 1)不会触发响应式更新。
性能瓶颈:深层嵌套对象需递归遍历,初始化性能较差。
Vue3 的优势

全面拦截:支持 in 操作符、for...in 循环及所有对象操作方法。
惰性优化:仅在访问属性时生成代理,减少初始化开销。
统一处理:对 Map、Set 等数据结构原生支持。
三、性能对比
Vue2:初始化时递归遍历全部属性,大规模数据场景性能较低。
Vue3:
按需代理,减少不必要的递归;
原生拦截数组操作,避免方法重写开销

Vue 怎么用 vm.$set() 解决对象新增属性不能响应的问题 ?
对象新增属性

const vm = new Vue({
  data: {
    user: { name: 'Alice' }
  }
});
// 错误方式:非响应式
vm.user.age = 25; // 视图不更新
// 正确方式
vm.$set(vm.user, 'age', 25); // 视图更新

数组索引修改

const vm = new Vue({
  data: {
    items: ['a', 'b']
  }
});
// 错误方式:非响应式
vm.items[1] = 'c'; // 视图不更新
// 正确方式
vm.$set(vm.items, 1, 'c'); // 视图更新

虚拟 DOM 的优缺点?
一、虚拟 DOM 的核心优势
性能优化

减少 DOM 操作次数:通过 Diff 算法对比新旧虚拟 DOM 树,仅更新差异部分,避免频繁操作真实 DOM 导致的重排与重绘。
批量更新机制:将多次数据变更合并为一次渲染操作,降低浏览器渲染开销。
跨平台支持

统一抽象层:虚拟 DOM 作为 JavaScript 对象,可适配不同平台(如浏览器、小程序、原生应用),实现“一次编码,多端运行”。
开发体验提升

声明式编程:开发者专注于数据逻辑,无需手动处理 DOM 更新细节。
框架自动优化:Vue 内部自动处理 Diff 流程,确保视图高效同步。
二、虚拟 DOM 的局限性
首次渲染性能损耗

构建虚拟 DOM 树和执行 Diff 算法会增加初始化渲染的计算成本,极端场景下可能比直接操作 DOM 更慢。
内存占用增加

虚拟 DOM 需维护完整的 JavaScript 对象树,对于大规模复杂应用可能占用更多内存
不适用简单场景

在小型项目或静态页面中,虚拟 DOM 的计算开销可能超出直接操作 DOM 的成本,导致性价比降低

vue的ChildNode diff算法
一、核心设计目标
最小化 DOM 操作:通过对比新旧子节点列表(children),复用可匹配的 DOM 节点,减少销毁和重建的开销。
时间复杂度优化:将传统树对比的 O(n³) 复杂度降为 O(n),仅同层比较,避免跨层级遍历。
二、算法实现策略
双端比较(Vue2)

四指针机制:通过旧头、旧尾、新头、新尾四个指针交叉扫描,优先匹配头尾节点。
复用条件:节点类型(tag)和 key 均相同时复用 DOM 元素。
典型步骤:
旧头 vs 新头 → 匹配则指针内移;
旧尾 vs 新尾 → 匹配则指针内移;
旧头 vs 新尾 → 匹配则移动节点至旧尾后;
旧尾 vs 新头 → 匹配则移动节点至旧头前。
快速 Diff(Vue3)

预处理阶段:跳过相同前缀和后缀节点,缩小对比范围。
最长递增子序列(LIS):对剩余节点计算最长稳定序列,最小化移动操作。
三、关键优化点
同层比较:仅对比同一层级的节点,跨层级移动视为销毁和新建。
Key 的作用:key 作为节点唯一标识,确保稳定复用(如列表渲染时)。
批量更新:差异计算完成后统一提交 DOM 修改,减少重排/重绘

你有对 Vue 项目进行哪些优化?
一、代码分割与懒加载
路由级懒加载
使用动态 import() 拆分路由组件,降低首屏加载体积(如 component: () => import('./views/Home.vue'))。
组件级按需加载
结合 <component :is> 和动态导入,仅在需要时加载非核心组件(如弹窗、侧边栏)。
二、响应式数据优化
规范数据声明
所有响应式数据必须通过 ref() 或 reactive() 包裹,避免直接赋值未代理的原始对象。
冻结静态数据
纯展示的长列表使用 Object.freeze() 冻结,跳过 Vue 的响应式代理,减少初始化开销。
三、渲染性能优化
虚拟 DOM 策略
列表渲染时始终为 v-for 设置唯一且稳定的 key(禁止使用 index),辅助 Diff 算法精准复用节点。
条件渲染优化
高频切换用 v-show(保留 DOM),低频展示用 v-if(销毁 DOM)。
虚拟滚动技术
万级数据列表采用 vue-virtual-scroller 等库,仅渲染可视区域 DOM 节点。
四、组件级优化
组件实例缓存
通过 <keep-alive> 缓存高频切换的组件(如 Tab 页),避免重复创建销毁。
逻辑复用与拆分
大型组件拆分为多个子组件,利用 Vue 的组件级更新特性减少渲染范围。
五、资源与构建优化
静态资源处理
图片启用懒加载(IntersectionObserver API),第三方库按需引入(如 lodash-es 的 import { debounce } from 'lodash-es')。
构建配置优化
生产环境关闭 SourceMap,启用 Terser 压缩混淆代码。
六、监控与调试
性能分析工具
使用 Vue Devtools 的 Performance 面板定位渲染瓶颈,结合 Chrome 的 Lighthouse 生成优化报告

vue3新增的响应式相关的函数
1.ref的理解
2.reactive的理解
3.ref和reactive的区别

一、ref 的理解
定义与作用

用于包装基本数据类型(如 number、string)或对象/数组,返回带有 .value 属性的响应式引用对象。
对象类型处理:若参数为对象或数组,内部自动调用 reactive() 进行深层代理。
核心原理

基本数据类型基于 Object.defineProperty 的 get/set 实现响应式;
对象类型通过 Proxy 代理实现深层响应式。
使用示例

const count = ref(0);        // 基本类型  
const user = ref({ name: '张三' });  
console.log(count.value);    // 访问值  
user.value.name = '李四';     // 修改嵌套属性  

适用场景

管理独立的基本类型数据(如计数器、开关状态);
需要显式 .value 操作的响应式引用场景。
二、reactive 的理解
定义与作用

深度代理普通对象或数组,生成响应式代理对象,直接访问属性即可触发更新。
限制:仅支持对象类型参数(数组视为对象)。
核心原理

基于 Proxy 拦截对象操作(属性读写、新增、删除等),通过 Reflect 操作源对象。
使用示例

const state = reactive({  
  count: 0,  
  list: [{ id: 1 }]  
});  
state.count++;               // 直接修改属性  
state.list[0].id = 2;        // 深层属性自动响应 

适用场景

管理复杂嵌套对象或数组(如用户信息、表单数据);
需要自动追踪深层属性变化的场景。
三、ref 与 reactive 的区别
对比维度 ref reactive
数据类型 支持基本类型和对象/数组 仅支持对象/数组
访问方式 需通过 .value 操作数据 直接访问属性
响应式原理 基本类型用 Object.defineProperty 全量基于 Proxy 代理
深层响应性 对象参数自动深层代理 默认深层代理所有属性
重新赋值 允许通过 .value = ... 重新赋值 无法直接替换整个代理对象(需修改内部属性)
场景选择建议
优先使用 ref:
简单数据、模板解构或需要显式控制响应式引用时;
优先使用 reactive:
复杂对象、性能敏感型操作或需要自动深层响应时。
总结示例

// ref 示例  
const toggle = ref(false);  
toggle.value = true;  

// reactive 示例  
const formData = reactive({  
  username: '',  
  permissions: ['read']  
});  
formData.permissions.push('write');  

通过合理选择 ref 和 reactive,可优化 Vue 项目的代码结构和响应式性能

自定义hooks(组合式函数)的规则和作用
一、核心规则
命名规范
函数名及文件名必须以 use 开头(如 useFetch、useMouse),遵循社区约定。
代码组织
将可复用逻辑封装为独立函数,通常放置在 src/hooks 目录下统一管理。
逻辑封装要求
内部必须使用 Vue 的组合式 API(如 ref、reactive、生命周期钩子)实现响应式逻辑。
数据与方法需显式解构返回,便于组件直接使用(如 return { data, update })

一、基本步骤
创建文件
在 src/hooks 目录下新建文件,命名格式为 useXxx.ts(如 useCounter.ts)。
编写逻辑
使用组合式 API(ref、reactive、生命周期等)封装功能,最后返回响应式数据或方法。
导出函数
将逻辑封装为函数并导出,供组件调用。

import { ref } from 'vue';  

export default function useCounter(initialValue = 0) {  
  const count = ref(initialValue);  

  const increment = () => count.value++;  
  const decrement = () => count.value--;  

  return { count, increment, decrement };  
}  

<script setup>  
import useCounter from '@/hooks/useCounter';  

const { count, increment } = useCounter(10);  
</script>  

<template>  
  <button @click="increment">{{ count }}</button>  
</template>  



// useWindowSize.js  
import { onMounted, onUnmounted, ref } from 'vue';  

export default function useWindowSize() {  
  const width = ref(window.innerWidth);  

  const updateSize = () => width.value = window.innerWidth;  
  onMounted(() => window.addEventListener('resize', updateSize));  
  onUnmounted(() => window.removeEventListener('resize', updateSize));  

  return { width };  
} 

Vue3自定义Hooks和Vue2时代Mixin的关系

Vue3 自定义 Hooks 与 Vue2 Mixin 的关系对比
一、设计目标
共同目标
两者均用于解决逻辑复用问题,避免代码重复。
差异定位
Mixin:通过对象合并实现逻辑混入,适用于 Vue2 的选项式 API;
Hooks:基于函数组合实现逻辑复用,专为 Vue3 的组合式 API 设计。
二、核心区别
维度 Vue2 Mixin Vue3 自定义 Hooks
实现方式 对象合并(隐式注入) 函数调用(显式导入)
数据来源 数据来源不透明,需手动追踪 数据通过返回值显式暴露,来源清晰
命名冲突 依赖合并策略,易引发冲突 支持解构重命名,避免冲突
类型支持 类型推断困难 天然支持 TypeScript
逻辑组织 分散在多个选项中(如 data、methods) 集中管理,按功能组合
三、演进关系
Mixin 的局限性
隐式依赖导致维护困难,跨 Mixin 交互可能产生副作用;
无法动态调整逻辑组合方式。
Hooks 的改进
通过函数封装实现高内聚、低耦合;
支持嵌套调用和动态组合,灵活性更高
Vue3 自定义 Hooks 是 Vue2 Mixin 的进化版,解决了 Mixin 的命名冲突、隐式依赖等问题,通过函数式编程提供更灵活、可维护的逻辑复用方案

setup配置(setup函数的参数)
1、props:父组件传来的属性值
2、context:上下文对象
一、props 参数
功能说明

接收父组件传递的属性值,类型为对象,包含组件声明过的所有 props。
未在 props 选项中声明的属性将无法通过 props 访问。
特性

响应式:props 中的数据是响应式的,但不可直接修改。
类型声明:需在组件选项中通过 defineProps 或 props 配置类型。
示例

// 子组件  
export default {  
  props: ['message'],  
  setup(props) {  
    console.log(props.message); // 输出父组件传递的值  
  }  
}  

二、context 参数
功能说明

提供组件上下文信息,包含 attrs、slots、emit 三个属性。
属性解析

attrs:未在 props 中声明的 DOM 属性或自定义属性。
slots:插槽内容,替代 Vue2 的 this.slots。 emit:触发自定义事件,替代 Vue2 的 this.emit。
示例

setup(props, { attrs, slots, emit }) {  
  console.log(attrs.class); // 获取未声明的 class 属性  
  emit('custom-event', 'data'); // 触发事件  
}  

三、对比 Vue2 的差异
props:Vue3 中直接通过参数获取,无需 this.props。 上下文:Vue2 通过 this 访问attrs/$slots,Vue3 通过 context 解构

Vue 3 中的 Composition API 带来了哪些好处?
Vue3 Composition API 的核心优势
一、代码组织与可维护性
逻辑聚合:允许按功能模块集中管理状态、计算属性和方法,解决 Options API 中代码分散问题(如 data、methods 分离导致的逻辑碎片化)。
模块化拆分:可将复杂组件拆分为多个独立的逻辑单元,提升大型项目的可读性和维护性。
二、逻辑复用能力
自定义 Hooks:通过函数封装实现逻辑复用(如 useFetch、useCounter),替代 Mixin 的隐式注入模式,避免命名冲突和依赖混乱。
动态组合:支持嵌套调用和灵活组合多个 Hooks,适应不同场景需求。
三、类型推导与开发体验
TypeScript 友好:函数式 API 天然支持类型推断,提供更完善的类型检查和 IDE 提示,降低大型项目的维护成本。
响应式精确控制:基于 ref 和 reactive 的细粒度响应式管理,配合 Proxy 实现的高效响应式系统,提升了性能与灵活性。
四、生命周期与副作用管理
统一管理副作用:在 setup 中通过 onMounted、onUnmounted 等函数集中处理生命周期逻辑,支持自动清理事件监听或异步任务。
替代 Options API 的限制:去除 beforeCreate/created 等钩子,简化生命周期管理流程

Vue 3.0 性能提升主要是通过哪几方面体现的?
一、响应式系统升级
基于 Proxy 的响应式实现

替代 Vue2 的 Object.defineProperty,直接代理对象属性,支持动态新增和删除属性的监听,避免递归初始化嵌套对象的性能损耗。
提升数组操作的响应性,支持监听数组索引和 length 属性的修改。
按需递归响应式

仅在访问对象属性时递归处理嵌套对象,减少初始化阶段的性能开销。
二、编译阶段优化
静态提升(Static Hoisting)

将模板中的静态节点(如纯文本、固定结构的 DOM)提取为常量,避免每次渲染重复创建虚拟节点。
Patch Flag 标记

在虚拟 DOM 的 diff 过程中,通过标记动态节点(如绑定值的插值、属性或事件)减少对比范围,跳过静态内容。
事件监听缓存

缓存事件处理函数,避免重复创建和绑定,降低更新时的性能损耗。
SSR 优化

服务端渲染时复用静态节点字符串,减少序列化与网络传输开销。
三、源码体积优化
Tree-Shaking 支持

模块化设计允许按需引入功能,未使用的 API(如 transition、filter)在打包时自动剔除,减小最终产物体积。
代码结构与静态提升

模板编译生成的代码更精简,运行时减少冗余逻辑调用。
四、虚拟 DOM 与渲染优化
Fragments 支持

组件模板支持多根节点,减少无意义的包装元素,提升渲染效率。
动态虚拟 DOM 对比

通过优化 diff 算法,仅对比动态节点内容,跳过静态结构的遍历

Vue 3.0 响应式系统的实现原理?
一、核心架构
Proxy 代理机制

使用 Proxy 替代 Vue2 的 Object.defineProperty,直接拦截对象的属性访问、赋值、删除等操作,解决了 Vue2 无法监听动态新增/删除属性和数组索引修改的缺陷。
结合 Reflect API 保证操作的正确性,例如通过 Reflect.get 和 Reflect.set 实现拦截逻辑的统一。
深层响应式处理

递归代理嵌套对象:在访问对象属性时,若属性值为对象,则递归调用 reactive() 创建嵌套 Proxy,实现深层响应式。
通过 WeakMap 缓存已代理对象,避免重复代理导致的性能损耗36。
二、依赖收集与更新触发
依赖追踪(Track)

在 Proxy 的 get 拦截器中,通过 track(target, key) 收集当前正在执行的副作用函数(如 effect 包裹的函数)与响应式数据的依赖关系。
更新派发(Trigger)

在 Proxy 的 set 或 deleteProperty 拦截器中,通过 trigger(target, key) 触发所有关联的副作用函数重新执行,更新视图或计算属性。
副作用函数(Effect)

effect(fn) 将传入函数包装为副作用函数,自动追踪其依赖的响应式数据,并在数据变化时重新执行。
三、核心 API 实现
reactive()

接收普通对象并返回其 Proxy 代理对象,通过 get/set/deleteProperty 拦截器实现响应式逻辑。

function reactive(target) {
  const handler = {
    get(target, key, receiver) {
      track(target, key); // 收集依赖
      const res = Reflect.get(target, key, receiver);
      return isObject(res) ? reactive(res) : res; // 递归代理嵌套对象
    },
    set(target, key, value, receiver) {
      const oldValue = target[key];
      const result = Reflect.set(target, key, value, receiver);
      if (oldValue !== value) trigger(target, key); // 触发更新
      return result;
    }
  };
  return new Proxy(target, handler);
}

ref() 与基础类型响应式

对基本类型(如 number)封装为对象,通过 .value 属性访问和修改,内部同样依赖 reactive() 的 Proxy 机制

vue3中定义全局属性
一、通过 app.config.globalProperties
定义全局属性
在 main.ts 中通过 app.config.globalProperties 挂载属性或方法,适用于简单全局变量。

const app = createApp(App);
app.config.globalProperties.$mode = 'production'; // 定义属性
app.config.globalProperties.$log = (msg: string) => console.log(msg); // 定义方法
app.mount('#app');

组件中使用

Options API:直接通过 this 访问(如 this.$mode)7。
Composition API:通过 getCurrentInstance() 获取实例后访问36。

import { getCurrentInstance } from 'vue';
const instance = getCurrentInstance();
const mode = instance?.appContext.config.globalProperties.$mode

export default {
  mounted() {
    console.log(this.$apiUrl); // 输出: https://api.example.com
    const currentTime = this.$formatTime(new Date());
    console.log(currentTime); // 输出格式化后的时间
  },
  methods: {
    fetchData() {
      // 使用全局属性发起请求
      fetch(`${this.$apiUrl}/data`)
        .then(response => response.json())
        .then(data => console.log(data));
    }
  }
}

watch与watchEffect

  1. watch
    特点:

显式声明依赖:需指定监听的具体数据源(响应式变量、getter 函数等)。
惰性执行:默认不会立即执行,仅在依赖变化时触发回调。
精确控制:可监听多个数据源,并获取变化前后的值。
使用场景:

需要监听特定数据的变化并执行副作用(如 API 请求)。
需要对比新旧值进行逻辑处理。
示例:

import { ref, watch } from 'vue';

const count = ref(0);
const user = ref({ name: 'Alice' });

// 监听单个 ref
watch(count, (newVal, oldVal) => {
  console.log(`count变化:${oldVal} → ${newVal}`);
});

// 监听 getter 函数(对象属性)
watch(
  () => user.value.name,
  (newName) => {
    console.log(`用户名更新为:${newName}`);
  }
);

// 监听多个数据源
watch([count, () => user.value.name], ([newCount, newName]) => {
  console.log(`count或name变化:${newCount}, ${newName}`);
}, { immediate: true }); // 立即执行一次
  1. watchEffect
    特点:

自动收集依赖:回调函数中使用的响应式变量会被自动追踪。
立即执行:首次调用时会立即运行一次。
简洁性:无需显式声明依赖,适合简单副作用。
使用场景:

依赖关系简单,无需手动指定监听源。
需要立即执行的副作用(如初始化数据)。
示例:

import { ref, watchEffect } from 'vue';

const count = ref(0);
const double = ref(0);

watchEffect(() => {
  double.value = count.value * 2; // 自动追踪 count
  console.log(`double更新为:${double.value}`);
});

count.value = 5; // 触发 watchEffect,输出:double更新为:10

核心区别
特性 watch watchEffect
依赖声明 显式指定 自动收集
执行时机 默认惰性(可配置 immediate) 立即执行
新旧值获取 可获取 (newVal, oldVal) 仅能访问当前值
适用场景 精确控制监听逻辑 简单副作用或依赖自动更新
总结:

需要精细控制时用 watch(如监听路由参数、表单字段)。
依赖简单且需立即响应时用 watchEffect(如计算属性衍生值)。

vue的动态添加的属性不会触发视图更新或者页面渲染错误
一、问题原因
Vue2 的 Object.defineProperty 限制

Vue2 通过 Object.defineProperty 在初始化时劫持对象已有属性,动态新增属性无法被自动追踪。
示例:直接赋值 this.obj.newProp = 'value' 数据变化但视图不更新。
Vue3 的 Proxy 优化但仍需注意

Vue3 改用 Proxy 实现响应式,但异步操作中动态添加属性可能因未被初始追踪而失效。
二、解决方案

  1. Vue2 的解决方式
    使用 Vue.set 或 this.$set
    显式声明响应式属性:
Vue.set(this.obj, 'newProp', 'value'); // 全局方法
this.$set(this.obj, 'newProp', 'value'); // 实例方法

替换整个对象
通过 Object.assign 或展开运算符创建新对象:

this.obj = Object.assign({}, this.obj, { newProp: 'value' });

数组操作
使用变异方法(如 push、splice)而非直接索引赋值5。

  1. Vue3 的解决方式
    使用 reactive 或 ref 包裹对象
    确保初始声明所有可能动态添加的属性:
const obj = reactive({ existingProp: '', _futureProp: null });

异步操作后手动触发更新
若动态添加属性,可配合 toRefs 或强制更新:

import { toRefs } from 'vue';
const { obj } = toRefs(reactiveState);

用过VUE 的自定义指令吗?自定义指令的方法有哪些

当修改data时Vue的组件重渲染是异步还是同步
在 Vue 中,当修改 data 的响应式属性时,组件的重渲染过程是异步的。这一机制是 Vue 响应式系统的核心优化策略,具体表现如下:

  1. 异步更新队列
    批量更新优化
    Vue 会将数据变更触发的更新操作推入一个异步队列,在下一个事件循环的 tick 中统一执行更新。这种设计避免了频繁的 DOM 操作,提升了性能。

更新触发时机
多次同步修改同一数据时,Vue 会合并这些变更,仅触发一次渲染流程。例如:

this.count = 1;
this.count = 2;
this.count = 3; 
// 最终只会触发一次视图更新
  1. 同步场景的例外
    虽然 Vue 的更新通常是异步的,但以下情况可能出现同步更新 DOM 的表现:

计算属性更新
当依赖的响应式数据变化时,计算属性会立即重新计算并触发 DOM 同步更新(但整体视图渲染仍需遵循异步队列)。
初始化渲染阶段
组件首次挂载时(mounted 生命周期),DOM 的生成和插入是同步完成的。

  1. 强制同步更新的风险
    若在代码中通过 this.forceUpdate() 强制同步更新,可能绕过异步队列导致性能下降。 正确做法是通过 this.nextTick() 等待异步更新完成后操作 DOM:
this.message = '更新后文本';
this.$nextTick(() => {
  console.log(this.$el.textContent); // 此时视图已更新
});
  1. 异步机制验证示例
export default {
  data() {
    return { count: 0 };
  },
  methods: {
    increment() {
      this.count++;
      console.log('当前数据:', this.count); // 立即输出更新后的值
      console.log('DOM未更新:', this.$el.textContent); // 输出旧值
      this.$nextTick(() => {
        console.log('DOM已更新:', this.$el.textContent); // 输出新值
      });
    }
  }
}

$nextTick和setTimout的区别
在 Vue.js 开发中,nextTick 与 setTimeout 均用于延迟代码执行,但其核心差异体现在任务类型、执行时机和应用场景上:

一、核心区别对比
对比维度 nextTick setTimeout
任务类型 微任务(Micro Task) 宏任务(Macro Task)
执行优先级 在当前事件循环末尾立即执行,优于宏任务 需等待当前微任务队列清空后执行
DOM 更新保障 确保 DOM 更新完成后执行回调 无法保证 DOM 已更新,存在操作旧 DOM 风险
实现机制 优先使用 Promise,降级至 MutationObserver 或 setImmediate,最后 setTimeout36 原生 JavaScript 定时器,直接通过浏览器 API 调用
典型应用场景 Vue 数据更新后的 DOM 操作(如表单验证后聚焦输入框) 通用延迟逻辑(如轮播图自动切换)
二、执行顺序验证示例

console.log(1);

this.$nextTick(() => console.log(2)); // 微任务,优先级高
setTimeout(() => console.log(3), 0);  // 宏任务,优先级低

console.log(4);

// 输出顺序:1 → 4 → 2 → 3 

三、关键实现原理
nextTick 内部机制

将回调函数推入 Vue 的异步更新队列,等待当前同步任务完成;
优先通过 Promise.resolve().then() 触发微任务;
兼容不支持 Promise 的环境时,降级使用 setTimeout(fn, 0)。
setTimeout 特性

即使延迟设为 0,回调仍会被推入宏任务队列;
实际执行时间受浏览器事件循环调度影响,可能存在不可预测的延迟

$nextTick和promise的关联

  1. 底层实现依赖 Promise
    微任务调度
    Vue 的 nextTick 在支持 Promise 的环境中,优先通过 Promise.resolve().then() 将回调推入微任务队列。这种设计确保了回调在当前事件循环的微任务阶段执行,优先级高于宏任务(如 setTimeout)。

降级策略
若环境不支持 Promise,则降级使用 MutationObserver 或 setImmediate,最终回退到 setTimeout。但现代浏览器和 Node.js 环境中,Promise 是默认首选。

  1. 执行顺序的优先级
    微任务队列中的顺序
    nextTick 的回调会先于其他通过 Promise.then() 注册的微任务执行。这是因为 Vue 内部对微任务队列进行了特殊处理,确保 DOM 更新相关的回调优先执行。

对比示例

Promise.resolve().then(() => console.log(1));
this.$nextTick(() => console.log(2));
// 输出顺序:2 → 1
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容