积累:05-vue3

54 阅读14分钟

1.Vue3.0 所采⽤的 Composition Api 与 Vue2.x 使⽤ 的 Options Api 有什么不同?

  • options API 结构

    image.png

    • options api组件状态写在data里,方法写在methods里,如果功能很多很复杂,对应的属性列表和方法列表会很长,导致组件难以阅读和理解
  • Composition Api

    • ⼀个功能所定义的所有 API 会放在⼀起 image.png

2. Vue3 做了哪些优化

🔧 一、设计优化(根本变革)

1.1 Proxy 替代 Object.defineProperty(响应式系统升级)

  • Vue2 使用 Object.defineProperty 拦截对象属性,只能劫持已有属性,新增/删除属性不能自动响应。
  • Vue3 使用 Proxy,可直接劫持整个对象,支持动态新增/删除属性,性能更高、覆盖面更广。

✅ 优势:更完整的响应式能力,性能更强,不需要为每个属性做递归绑定。


1.2 Composition API(组合式 API)

  • 新增 setup()refreactivecomputedwatch 等。
  • 更好地组织逻辑复用(避免 Vue2 的 mixin 命名冲突和逻辑分散问题)。

✅ 优势:逻辑更清晰,利于代码重用,适合大型项目开发。


1.3 更好支持 TypeScript

  • Vue2 对 TS 支持不友好(Options API 类型推导困难)。
  • Vue3 全面重构,采用类型增强的编译器和组合式 API,更适合 TS 项目。

✅ 优势:代码更健壮,开发体验更好,IDE 自动补全更精准。


🚀 二、性能优化

2.1 编译时优化(编译提升)

Vue3 编译器会进行静态标记(Static Tree Hoisting、Patch Flag):

  • 静态提升:将不变的部分提升到渲染函数之外。
  • Patch Flag:只对变化部分打补丁,跳过静态部分的 diff。

✅ 优势:避免不必要的更新,提高渲染效率。


2.2 更快的虚拟 DOM

  • Vue3 的虚拟 DOM 更轻量、更优化。
  • 使用 Block Tree 技术,追踪组件内的动态节点,跳过静态节点对比。

2.3 Tree Shaking 更好

  • Vue3 全面模块化(ESM),使用了 rollup 编译器 + ESM 输出。
  • 未使用的 API 不会被打包(如 v-modelteleport 等),减小包体积。

✅ 优势:体积更小,按需导入。


🏗 三、架构升级

3.1 Fragment 支持

  • Vue2:组件只能有一个根节点。
  • Vue3:允许组件返回多个根节点(Fragment)。

3.2 Teleport(传送门)

  • 可以将组件的 DOM 渲染到 DOM 树的任意位置(如弹窗、模态框)。

3.3 Suspense

  • 异步组件加载时渲染 fallback 内容,配合 <Suspense>

3.4 自定义渲染器(Custom Renderer)

  • Vue3 内部将渲染逻辑抽象为 Renderer,可以扩展到 Native、WebGL、Canvas、终端等。

🔧 如:Vue + Vue NativeScript、Vue + Vue Terminal Renderer

🧠 四、功能增强

4.1 多个 v-model

<MyInput v-model:title="title" v-model:content="content" />

4.2 Emits 明确声明

defineEmits(['update:value'])

4.3 更好的异步组件支持

  • Vue3 异步组件加载支持超时、错误捕获、loading 等细节。

3. 你用vue3写过组件吗?如果让你实现一个modal你会怎么设计

  • 设计目标
    • 使用composition api
    • 支持插槽内容插入
    • 支持遮罩点击,ESC关闭
    • 支持使用v-model控制显示隐藏
    • 使用 <Teleport>将Model移动到body外层避免层级冲突
    • 可复用,外部引入使用

🧩 Modal.vue 实现

 <!-- components/Modal.vue -->
 <template>
   <Teleport to="body">
     <Transition name="fade">
       <div v-if="modelValue" class="modal-overlay" @click.self="handleClose">
         <div class="modal-container" ref="modalRef">
           <slot></slot>
           <button class="close-btn" @click="handleClose">×</button>
         </div>
       </div>
     </Transition>
   </Teleport>
 </template>

 <script setup lang="ts">
 import { onMounted, onBeforeUnmount, ref } from 'vue'

 const props = defineProps<{
   modelValue: boolean
 }>()

 const emit = defineEmits(['update:modelValue'])

 const handleClose = () => {
   emit('update:modelValue', false)
 }

 const handleKeyDown = (e: KeyboardEvent) => {
   if (e.key === 'Escape') handleClose()
 }

 onMounted(() => {
   window.addEventListener('keydown', handleKeyDown)
 })

 onBeforeUnmount(() => {
   window.removeEventListener('keydown', handleKeyDown)
 })

 const modalRef = ref()
 </script>

 <style scoped>
 .modal-overlay {
   position: fixed;
   top: 0;
   left: 0;
   right: 0;
   bottom: 0;
   background-color: rgba(0, 0, 0, 0.6);
   display: flex;
   align-items: center;
   justify-content: center;
   z-index: 999;
 }

 .modal-container {
   background: white;
   padding: 1.5rem;
   border-radius: 8px;
   min-width: 300px;
   position: relative;
 }

 .close-btn {
   position: absolute;
   right: 10px;
   top: 10px;
   background: transparent;
   border: none;
   font-size: 1.5rem;
 }
 .fade-enter-active,
 .fade-leave-active {
   transition: opacity 0.2s;
 }
 .fade-enter-from,
 .fade-leave-to {
   opacity: 0;
 }
 </style>

🧪 使用方式

    
    <script setup lang="ts">
    import { ref } from 'vue'
    import Modal from '@/components/Modal.vue'

    const showModal = ref(false)
    </script>

    <template>
      <button @click="showModal = true">打开弹窗</button>

      <Modal v-model="showModal">
        <h2>你好 Modal</h2>
        <p>这里是内容</p>
      </Modal>
    </template>

4.Vue3.0性能提升主要是通过哪⼏⽅⾯体现的?

优化点描述
1. Proxy 替代 Object.defineProperty响应式系统升级,性能大幅提升,支持更多数据类型
2. Tree-shaking 友好采用 ES Module 编写,可按需打包,减小体积
3. 编译优化 (静态提升/静态节点 patch 优化)更智能地标记和提升静态节点,减少 runtime 开销
4. Composition API更好的逻辑复用,减少组件代码体积
5. 更快的 Virtual DOMDiff 算法更高效,重写了 patch 过程
6. 更小更快的初始渲染改善首次渲染耗时
7. SSR 性能翻倍Vue3 SSR 渲染速度提升 2 倍以上
8. 更好的 TypeScript 支持重写了源码,完全使用 TS 编写,类型提示增强
1. ✅ Proxy 替代 defineProperty
  • Vue 2 使用 Object.defineProperty() 劫持对象属性,但存在以下问题:

    • 无法监听新增/删除属性
    • 数组的变更方法需要 hack(如 push
    • 深层嵌套结构开销大
  • Vue 3 使用 Proxy 实现响应式:

    • 可监听所有操作(读、写、删除、in 操作符、枚举等)
    • 支持数组/Map/Set 等复杂结构
    • 性能大幅提升
2. ✅ Tree-shaking 更友好
  • Vue 3 采用全模块化 ES Module 写法
  • 未使用的 API 可以被构建工具(如 webpack, rollup, Vite)摇树优化掉
    // 只用到 ref 和 watch
    import { ref, watch } from 'vue'

结果:打包体积更小!

3. ✅ 编译优化:静态提升、Patch 标记

Vue 3 编译阶段标记了静态节点(VNode),例如:

    <!-- 静态内容 -->
    <div class="title">Hello</div>

**不会在每次更新中重新创建/比较这个节点,节省 Diff 与 patch 的开销**

  • 静态提升
    • vue3 中对不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用
  • 事件监听缓存
    • 默认情况下绑定事件⾏为会被视为动态绑定,所以每次都会去追踪它的变化
4. ✅ Composition API 更适合逻辑复用
  • 逻辑集中、复用性强
  • 替代 mixin 中变量冲突、命名模糊的问题

也间接减少了组件嵌套结构带来的渲染负担。

5. ✅ Virtual DOM 优化
  • Vue 3 的虚拟 DOM 是重新设计的(比 Vue 2 更轻、更快)
  • 静态提升让 Virtual DOM 结构更 lean
  • 使用 patchFlag 精准定位更新节点,减少不必要的重渲染
6. ✅ 初始渲染更快

首次渲染(mount)耗时更短,结合静态节点提升、template 编译优化。

7. ✅ 服务端渲染 SSR 性能翻倍

Vue 3 SSR 模块重写,提升如下:

  • 多核并发渲染
  • 更高效的 string concat
  • 组合式 API 更易按需加载
  • 当静态内容⼤到⼀定量级时候,会⽤ createStaticVNode ⽅法在客户端去⽣成⼀个static node,这 些静态 node ,会被直接 innerHtml ,就不需要创建对象,然后根据对象渲染
8. ✅ TypeScript 支持原生好
  • Vue 2 是 JS 写的,加了 TS 类型声明,支持不完整
  • Vue 3 是用 TS 重写的,类型安全、推导好、IDE 支持强

🚀 性能对比(来自官方 benchmark)

项目Vue 2.xVue 3.0
初始渲染时间快 1.2~2 倍
内存占用更少
更新节点数量多时更顺滑
SSR 吞吐量一般提升 2 倍以上

5. Vue3.0⾥为什么要⽤ Proxy API 替代 defineProperty API ?

一、Vue 2:使用 Object.defineProperty

Object.defineProperty(obj, 'key', {
  get() { ... },
  set(newVal) { ... }
})
问题:
  1. 只能劫持对象已有的属性
    新增/删除属性时要用 Vue.set/delete 手动处理。
  2. 数组劫持有限
    数组的索引或长度变动无法被监听,只能通过重写数组方法(如 pushsplice)解决,属于 hack。
  3. 递归性能差
    需要递归遍历整个对象才能做响应式处理,对深层对象、大对象开销非常大。

✅ 二、Vue 3:使用 Proxy

    const proxy = new Proxy(target, {
      get(target, key) { ... },
      set(target, key, value) { ... },
      deleteProperty(target, key) { ... }
    })
优点
  • 无需遍历 : vue3使用lazy-observer,只有访问属性的时候才会创建响应式,更快更省内存
  • 全面监听能力 : 能监听新增、删除、数组索引、长度变化
  • 支持 Map / Set / WeakMap / WeakSet : Vue 2 无法监听这些类型,Vue 3 可完美支持。 |
  • 更干净的源码结构 : 不再需要重写数组方法等“黑魔法”,更优雅、更可维护

✅ 三、Tree-shaking 友好性 vue3 使用模块化、函数式api (如reactive,ref,computed等) 构建响应式系统,不再强依赖全局类或原型链 - 未使用的API可以被构建工具剔除 - 最终结果就是打包体积更小

  • 是基于ES6 模板语法(import和export)

✅ 四、Proxy 带来的响应式系统变化 上面说过了

6. Vue3新特性?

一. 性能优化相关
    1. v-once
    • 功能:只渲染一次元素或者组件,之后不会重新渲染
    • 适用场景:静态内容或不需要更新的部分。
    • <p v-once>这个内容只会渲染一次</p>
    1. v-memo
    • 功能:对动态节点进行条件性的缓存,只在绑定值变更的时候重新渲染
    • 优势:用于部分内容的优化,其余内容不变的复杂结构,减少渲染开销
    <div v-memo="[count]">
      <p>{{ count }}</p>
      <p>静态部分不会重新渲染</p>
    </div>
    
    
二. 响应式系统的升级(Proxy代替defineProperty)
    1. 更强大的响应式系统
    • 原理变化:Vue 3 使用 Proxy 替代了 Vue 2 的 Object.defineProperty
    • 优势
      • 支持数组索引和对象属性的新增/删除响应式
      • 性能更好
      • 可以监听整个对象而不是单个属性
    1. reactive & ref
    • reactive():把一个对象变成响应式对象
    • ref():用于包装原始值
    • 示例:
     const count = ref(0)
     const state = reactive({ count: 0 })
    
三、组合式 API(Composition API)

1. setup()

  • 所有组合式逻辑的起点,组件中逻辑写在这里。

2. ref / reactive / computed / watch / watchEffect

  • 更灵活地组合逻辑,避免 Option API 中的“逻辑分散”。

3. 生命周期钩子(如 onMountedonUpdated

  • 替代原来的 mounted 等选项,适用于组合式 API。

🧩 四、其它新指令与增强

1. v-model 多个绑定

  • Vue 3 支持多个 v-model 绑定:
        <MyComponent v-model:title="title" v-model:content="content" />

2. v-is 支持动态组件标签

  • 用于 <component> 的语法糖:
        <button v-is="dynamicComponent"></button>

3. v-bind 支持多个属性绑定:

    <div v-bind="{ id: someId, class: someClass }"></div>

🧪 五、Fragments / Teleport / Suspense

1. Fragments(多根节点支持)

  • Vue 3 组件可以返回多个根节点了!

2. Teleport(传送渲染)

  • 渲染 DOM 到组件外部:
        <teleport to="body">
          <div class="modal">这是模态框</div>
        </teleport>

3. Suspense(异步组件加载占位)

  • 异步组件加载时显示 fallback 内容:
        <Suspense>
          <template #default>
            <AsyncComponent />
          </template>
          <template #fallback>
            <div>加载中...</div>
          </template>
        </Suspense>

📚 六、TypeScript 支持更好

  • Vue 3 从架构上原生支持 TypeScript,包括:

    • 类型推导增强
    • defineComponent 提供类型约束
    • Volar 插件支持 <script setup lang="ts"> 类型检查

✅ 总结表格

特性说明
v-once内容只渲染一次,静态内容优化
v-memo条件性缓存,提高动态部分性能
Proxy 响应式系统全面替代 defineProperty,性能和覆盖范围更广
Composition API更灵活的组合逻辑方式
Fragments支持组件多根节点
Teleport将子组件渲染到 DOM 的其他地方
Suspense异步组件加载支持 fallback
多个 v-model支持多个双向绑定
v-is支持动态组件语法糖
TS 原生支持类型推导更强、编辑器体验更好

🚀 Vue3 响应式核心 API

1. ref

  • 作用:用于包装基本类型值(number、string、boolean 等),或者对象。返回一个带有 .value 的响应式引用。

  • 特点

    • 在模板中 .value 会自动解包。
    • 在 JS 中必须通过 .value 访问。
import { ref } from 'vue';

const count = ref(0);

function increment() {
  count.value++;
}

适用场景

  • 基本类型(数字、字符串、布尔)
  • 需要一个“独立可变值”的时候,比如定时器 ID、是否显示对话框等。

2. reactive

  • 作用:将对象变为深层响应式,返回一个 Proxy 包装的对象。

  • 特点

    • 内部属性会递归转为响应式。
    • 修改属性时,会触发视图更新。
import { reactive } from 'vue';

const state = reactive({
  count: 0,
  user: { name: 'Tom', age: 20 }
});

state.count++;
state.user.age++;

适用场景

  • 管理多个字段的复杂对象/数组
  • 类似 Vue2 的 data()

3. ref vs reactive

对比点refreactive
数据类型基本类型/对象只能对象/数组
访问方式.value直接访问
内部实现RefImpl 包装Proxy 包装
解构后响应性丢失(除非配合 toRefs丢失(除非配合 toRefs

4. toRef

  • 作用:把对象某个属性转成 ref,保持响应式连接。

  • 特点

    • 修改 ref.value 会影响原对象。
    • 修改原对象也会影响这个 ref。
import { reactive, toRef } from 'vue';

const state = reactive({ count: 0 });
const countRef = toRef(state, 'count');

countRef.value++;   // state.count 也会变
state.count = 10;   // countRef.value 也变

适用场景

  • 当需要将 reactive 中的某个字段 单独传递/解构 时,避免丢失响应式。

5. toRefs

  • 作用:把整个对象的所有属性都转成 ref
import { reactive, toRefs } from 'vue';

const state = reactive({ name: 'Tom', age: 20 });
const { name, age } = toRefs(state);

name.value = 'Jerry'; // state.name 也变了

适用场景

  • setup()return 响应式对象时,避免丢失响应性。
setup() {
  const state = reactive({ name: 'Tom', age: 20 });
  return { ...toRefs(state) }; // 保持解构后的属性仍然响应式
}

6. shallowRef & shallowReactive

  • shallowRef:只有 .value 是响应式的,内部对象不做深层响应式。
const obj = shallowRef({ a: 1 });
obj.value.a = 2; // 不会触发视图更新
obj.value = { a: 3 }; // 会触发更新
  • shallowReactive:只有对象的第一层属性是响应式的,深层属性不响应。
const state = shallowReactive({ foo: { bar: 1 } });
state.foo.bar = 2; // 不会触发更新
state.foo = { bar: 3 }; // 会触发更新

适用场景

  • 性能优化,避免深层递归代理。
  • 当只关心浅层属性变化时。

7. readonly

  • 作用:创建一个只读的响应式代理,防止修改。
const original = reactive({ count: 0 });
const ro = readonly(original);

ro.count++; // 报错(开发环境警告)

适用场景

  • 向子组件传递只读状态,避免被意外修改。

8. computed

  • 作用:基于响应式数据派生出新数据,具备缓存。
import { ref, computed } from 'vue';

const count = ref(1);
const double = computed(() => count.value * 2);

count.value = 2; // double.value 自动更新

适用场景

  • 依赖已有数据的派生值。
  • 避免重复计算。

9. watch & watchEffect

  • watch:侦听指定数据。
const count = ref(0);
watch(count, (newVal, oldVal) => {
  console.log('count changed:', newVal, oldVal);
});
  • watchEffect:自动收集依赖,立即执行一次。
watchEffect(() => {
  console.log('count is', count.value);
});

🎯 总结记忆口诀

  • ref:小(基本类型/单值),要 .value
  • reactive:大(对象/数组),直接用
  • toRef:取对象的某个属性当 ref
  • toRefs:批量把对象转 ref,常用于解构
  • shallowRef / shallowReactive:只做浅层响应式,性能优化
  • readonly:只读,保护数据
  • computed:缓存派生值
  • watch / watchEffect:副作用侦听

vue3父子组件通讯

1. 父传子(Props)

父组件通过 props 传递数据,子组件通过 defineProps 接收。

子组件(Child.vue):

<script setup>
import { defineProps } from 'vue'

const props = defineProps({
  msg: String,
  count: Number
})
</script>

<template>
  <div>
    <p>子组件收到:{{ msg }} - {{ count }}</p>
  </div>
</template>

父组件(Parent.vue):

<template>
  <Child :msg="message" :count="num" />
</template>

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const message = ref('Hello Vue3')
const num = ref(10)
</script>

2. 子传父(emit)

子组件通过 emit 派发事件,父组件监听并接收。

子组件(Child.vue):

<script setup>
import { defineEmits } from 'vue'

const emit = defineEmits(['updateMessage'])

function sendMsg() {
  emit('updateMessage', '子组件发来的消息')
}
</script>

<template>
  <button @click="sendMsg">点我发消息给父组件</button>
</template>

父组件(Parent.vue):

<template>
  <Child @updateMessage="getMsg" />
  <p>父组件接收:{{ msg }}</p>
</template>

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const msg = ref('')

function getMsg(val) {
  msg.value = val
}
</script>

3. v-model 语法糖(父子双向绑定)

Vue3 中可以在 子组件中通过 defineProps + defineEmits 实现双向绑定

子组件(Child.vue):

<script setup>
const props = defineProps({
  modelValue: String
})
const emit = defineEmits(['update:modelValue'])

function updateValue(e) {
  emit('update:modelValue', e.target.value)
}
</script>

<template>
  <input :value="props.modelValue" @input="updateValue" />
</template>

父组件(Parent.vue):

<template>
  <Child v-model="text" />
  <p>父组件:{{ text }}</p>
</template>

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const text = ref('初始值')
</script>

4. ref 获取子组件实例

父组件可以通过 ref 调用子组件的方法或访问数据。

子组件(Child.vue):

<script setup>
import { ref } from 'vue'

const msg = ref('子组件数据')

function sayHello() {
  alert('子组件方法被调用啦!')
}

defineExpose({
  msg,
  sayHello
})
</script>

父组件(Parent.vue):

<template>
  <Child ref="childRef" />
  <button @click="callChild">调用子组件</button>
</template>

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'

const childRef = ref(null)

function callChild() {
  console.log(childRef.value.msg) // 访问子组件数据
  childRef.value.sayHello()       // 调用子组件方法
}
</script>

总结

  • 父传子props
  • 子传父emit
  • 双向绑定v-modelmodelValue + update:modelValue
  • 父调用子ref + defineExpose

5. EventBus (事件总线)

在 Vue2 里常用 new Vue() 做事件总线,但在 Vue3 里 Vue 实例不再支持这种用法,所以推荐用 mitt(轻量级事件库)。

eventBus.js:

import mitt from 'mitt'
const bus = mitt()
export default bus

子组件(Child.vue):

<script setup>
import bus from './eventBus.js'

function sendMsg() {
  bus.emit('custom-event', '子组件通过 EventBus 发的消息')
}
</script>

<template>
  <button @click="sendMsg">通过EventBus发消息</button>
</template>

父组件(Parent.vue):

<script setup>
import bus from './eventBus.js'
import { onMounted, onBeforeUnmount, ref } from 'vue'
import Child from './Child.vue'

const msg = ref('')

onMounted(() => {
  bus.on('custom-event', (val) => {
    msg.value = val
  })
})

onBeforeUnmount(() => {
  bus.off('custom-event')
})
</script>

<template>
  <Child />
  <p>父组件收到:{{ msg }}</p>
</template>

6. Vuex(状态管理)

Vue3 中依然支持 Vuex(但推荐 Pinia 作为替代)。Vuex 的核心是 状态集中管理,父子都可以从全局状态里读写。

store/index.js:

import { createStore } from 'vuex'

const store = createStore({
  state: {
    msg: '全局共享消息'
  },
  mutations: {
    setMsg(state, payload) {
      state.msg = payload
    }
  },
  actions: {
    updateMsg({ commit }, payload) {
      commit('setMsg', payload)
    }
  }
})

export default store

main.js:

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

createApp(App).use(store).mount('#app')

子组件(Child.vue):

<script setup>
import { useStore } from 'vuex'

const store = useStore()

function changeMsg() {
  store.commit('setMsg', '子组件修改了全局消息')
}
</script>

<template>
  <button @click="changeMsg">修改全局消息</button>
</template>

父组件(Parent.vue):

<script setup>
import { useStore } from 'vuex'
import { computed } from 'vue'
import Child from './Child.vue'

const store = useStore()
const msg = computed(() => store.state.msg)
</script>

<template>
  <Child />
  <p>父组件从 Vuex 读取:{{ msg }}</p>
</template>

总结一下 Vue3 父子通信方式:

  • props:父传子
  • emit:子传父
  • v-model:父子双向绑定
  • ref + defineExpose:父调用子方法/数据
  • provide & inject:跨层级传递(祖先 → 后代)
  • EventBus(mitt) :任意组件通信(适合中小型项目)
  • Vuex / Pinia:全局状态管理(适合中大型项目)