Skip to content

Vue3:响应式重构、Composition API 与现代工程化

Vue3 可以看成 不是对 Vue2 的小修小补,而是一轮面向现代工程化、复杂业务和类型系统体验的系统升级

把主线压成一句话:Vue3 把 Vue 从一套经典组件框架,推进成了一套更适合复杂应用组织、逻辑复用和现代工具链协作的前端框架。

1. Vue3 是什么

Vue3 是 Vue 框架的新一代主流版本。

它最核心的变化主要落在这几层:

  1. 响应式系统从 Object.defineProperty 升级到 Proxy
  2. 代码组织从以 Options API 为主,扩展为更强的 Composition API
  3. 对 TypeScript 更友好
  4. 编译器和运行时优化更细
  5. 整体工程边界更现代

2. Vue3 主要解决什么问题

Vue2 并不是不能做复杂项目,但随着项目规模上来,几个问题会越来越明显:

  1. 复杂组件里逻辑分散
  2. 逻辑复用不自然
  3. 类型系统体验一般
  4. 响应式机制有历史边界
  5. 编译和运行优化空间还可以继续挖

Vue3 的很多设计,本质上都是在回答这些问题。

3. Vue3 最该抓住的 5 个关键词

理解 Vue3 时,最值得抓住的是:

  1. Proxy
  2. Composition API
  3. setup
  4. script setup
  5. 更现代的应用和工具链边界

4. Vue3 的响应式为什么改成 Proxy

Vue3 最经典的一条变化,就是响应式系统升级。

把这层变化拉开:

  1. Vue2 更像“改造已有属性”
  2. Vue3 更像“给整个对象包一层代理”

这带来的好处包括:

  1. 对新增属性、删除属性支持更自然
  2. 数组处理更统一
  3. 更容易支持 MapSet 这类结构
  4. 整体扩展能力更强

所以讲“为什么 Vue3 改用 Proxy”时,不要只说:因为 Proxy 更高级。

更准确的说法应该是:因为 Vue2 的响应式在对象新增、删除、数组和复杂数据结构上存在边界,Vue3 改用 Proxy 后,响应式系统更完整,也更适合现代应用。

4.1 Vue3 的数据绑定到底是什么

Vue3 的“数据绑定”也不应该只理解成一句:数据变了,页面会变。

它其实仍然是两层组合:

  1. 组件状态驱动视图渲染
  2. 表单这类交互场景,再通过事件把用户输入回写状态

例如:

vue
<template>
  <div>
    <p>{{ count }}</p>
    <input v-model="keyword" />
  </div>
</template>

这里至少发生了两件事:

  1. 模板读取 countkeyword,让渲染逻辑依赖这些响应式数据
  2. 输入事件再把用户输入写回 keyword

所以 Vue3 里也不是脱离单向渲染的“纯双向绑定模型”,而是:以状态驱动视图为主,再通过语法糖把输入回写接上。

4.2 Vue3 是怎么做到“数据一变,视图跟着变”的

可以把 Vue3 的主链路压缩成 5 步:

  1. ref / reactive 创建响应式数据
  2. 组件渲染时读取这些数据
  3. 读取时触发 track,收集当前活动副作用 effect
  4. 写入时触发 trigger,通知相关 effect
  5. 相关渲染副作用通过调度器重新执行,最终 patch DOM

如果只记一条主线,可以记成:Proxy 拦截 -> track 收集依赖 -> trigger 触发依赖 -> scheduler 调度更新 -> patch DOM

下面这段可以当成“Vue3 响应式源码风格的简化版”来看:

ts
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      // target: 被代理的原始对象
      // key: 当前读取的属性名,例如 state.count 里的 count
      // receiver: 本次读取操作的接收者,通常就是代理对象本身
      // 读取属性时,记录“当前 effect 依赖了 target 的这个 key”
      track(target, key)
      // 通过 Reflect.get 保持 JS 原生取值语义:
      // 1. 正确处理 getter
      // 2. 正确处理原型链
      // 3. 让 getter 内部的 this 指向 receiver,而不是错误地落回原始对象
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      // value: 准备写入的新值
      // 通过 Reflect.set 真正完成赋值,并保持 setter / 原型链等行为正确
      const result = Reflect.set(target, key, value, receiver)
      // 写入属性后,通知依赖这个 key 的 effect 重新执行
      trigger(target, key)
      // Proxy 的 set 拦截器需要返回布尔值,表示本次写入是否成功
      return result
    }
  })
}

这段代码最重要的意思是:

  1. 读取属性时走 track
  2. 写入属性时走 trigger
  3. Vue3 的响应式更像“给对象包了一层代理”

4.2.1 refreactive 在底层关系上可以怎么理解

很多人学到这里会继续追问:

refreactive 到底是不是两套完全独立的东西。

更稳的理解方式是:

  1. reactive 更像“代理整个对象”
  2. ref 更像“把单个值包进一个带 .value 的响应式壳里”
  3. 它们最终都会接入同一套依赖收集和触发更新机制

可以把 ref 当成下面这种“源码风格的简化实现”来看:

ts
/**
 * createRef
 * 功能:把一个普通值包装成带 `.value` 的响应式引用对象。
 * 核心职责:
 * 1. 读取 `.value` 时收集依赖
 * 2. 修改 `.value` 时触发依赖
 * 参数:
 * - rawValue: 原始值,可以是基本类型,也可以是对象
 * 返回值:
 * - 一个带 `value` 访问器的引用对象
 */
function createRef(rawValue) {
  let value = rawValue

  return {
    get value() {
      track(this, 'value')
      return value
    },
    set value(newValue) {
      if (newValue === value) return
      value = newValue
      trigger(this, 'value')
    }
  }
}

这段代码最想说明的是:refreactive 表面用法不同,但底层仍然都在围绕“读取时 track、写入时 trigger”这条主线工作。`

4.3 tracktriggereffect 分别在做什么

这 3 个词,是理解 Vue3 响应式系统的核心。

4.3.1 track

track 可以看成 在读取数据时,记录“当前这段逻辑依赖了哪个目标对象的哪个属性”

也就是在回答:谁正在用我。

4.3.2 trigger

trigger 可以看成 在写入数据时,把依赖这个属性的副作用重新唤起

也就是在回答:我变了,该通知谁。

4.3.3 effect

effect 可以看成 一段依赖响应式数据、需要在依赖变化时重新执行的逻辑

在 Vue3 语境里,可以把组件渲染理解成:一个特殊的响应式副作用。

所以组件之所以能“自动更新”,本质上是因为:组件渲染过程本身就被包进了一套响应式副作用机制里。

如果用一段更贴近底层实现的简化代码来理解,大致可以这样看:

ts
const targetMap = new WeakMap()

function track(target, key) {
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 第一层按目标对象建立依赖表
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }

  let dep = depsMap.get(key)
  if (!dep) {
    // 第二层按属性 key 建立 effect 集合
    dep = new Set()
    depsMap.set(key, dep)
  }

  if (activeEffect) {
    // 当前正在执行的 effect 读取了这个属性,于是建立依赖关系
    dep.add(activeEffect)
  }
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  const dep = depsMap?.get(key)
  if (!dep) return

  dep.forEach(effect => {
    if (effect.scheduler) {
      // 有调度器时,先交给调度器决定什么时候执行
      effect.scheduler()
    } else {
      // 没有调度器时,直接重跑 effect
      effect()
    }
  })
}

这段代码想表达的重点是:

  1. target -> key -> effects 之间会建立映射关系
  2. 依赖不是散乱通知,而是按目标对象和具体属性精确触发
  3. 调度器存在时,不一定马上执行,而是可以交给统一更新机制

4.3.4 effect 自己内部大致长什么样

如果继续往下追一层,很多人会想知道:activeEffect 到底是怎么来的,为什么 track 时能知道“当前是谁在读数据”。`

可以把 effect 非常简化地理解成下面这样:

ts
let activeEffect = null

/**
 * effect
 * 功能:把一段函数包装成响应式副作用。
 * 核心职责:
 * 1. 执行前把当前副作用挂到 activeEffect 上
 * 2. 执行过程中让 track 有机会收集它
 * 3. 执行完后恢复现场
 * 参数:
 * - fn: 真正需要响应式联动的逻辑
 * - options: 可选配置,例如 scheduler
 * 返回值:
 * - runner: 一个可重复执行的副作用函数
 */
function effect(fn, options = {}) {
  const runner = () => {
    const parent = activeEffect
    activeEffect = runner
    try {
      return fn()
    } finally {
      activeEffect = parent
    }
  }

  runner.scheduler = options.scheduler
  runner()
  return runner
}

这样再回头看 track,就更容易明白了:

  1. effect 执行前把自己挂到 activeEffect
  2. 渲染过程中一旦读取响应式属性,就会进入 track
  3. track 看到当前有 activeEffect,于是把它收集起来
  4. 后面属性变化时,trigger 就能把这段副作用重新唤起

所以更完整的底层主线是:effect 执行 -> activeEffect 建立上下文 -> getter 中 track 收集 -> setter 中 trigger 通知

如果把这条链路再压成一张图,可以这样看:

mermaid
flowchart TD
    A[组件渲染读取 state.count] --> B[Proxy get]
    B --> C[track target key]
    C --> D[记录 activeEffect]

    E[state.count = 2] --> F[Proxy set]
    F --> G[trigger target key]
    G --> H[找到依赖当前 key 的 effects]
    H --> I[scheduler 入队]
    I --> J[重新执行组件渲染 effect]
    J --> K[生成新 vnode]
    K --> L[patch DOM]

4.4 Vue3 的更新调度为什么不是“改一次就渲染一次”

Vue3 也不会每次状态变化都立刻同步直接改 DOM。

更常见的流程是:

  1. 状态变化触发依赖
  2. 依赖进入调度器
  3. 同一轮事件循环里的多次更新尽量合并
  4. 最后统一刷新组件

这套机制的价值在于:

  1. 避免重复渲染
  2. 合并多次状态变更
  3. 让 DOM 更新成本更可控

这也是为什么 nextTick 仍然重要,因为:数据变更和 DOM 完成更新之间,通常隔着一层调度与批处理。

可以再看一段“组件渲染 effect + scheduler”的简化写法:

ts
const update = effect(
  () => {
    // 组件重新执行渲染逻辑,得到下一棵 vnode 树
    const vnode = renderComponentRoot(instance)
    // 把旧树和新树做 patch,落到真实 DOM
    patch(instance.subTree, vnode)
    instance.subTree = vnode
  },
  {
    // 响应式状态变化时,不直接重跑,而是先入调度队列
    scheduler: () => queueJob(instance.update)
  }
)

instance.update = update

这段代码最值得先理解的是:

  1. 组件渲染本身就是一个 effect
  2. 状态变化后,并不是直接同步重跑,而是先进调度队列
  3. 真正的页面更新,仍然落在“重新生成 vnode -> patch”这条主线上

4.4.1 调度器队列在做什么

如果只知道“有调度器”,还是容易停留在抽象层。

更贴近运行时的理解是:

  1. 每个组件更新任务会先进入队列
  2. 队列会去重,避免同一个组件在一轮里重复进入多次
  3. 刷新时统一按顺序执行这些任务

下面这段可以当成“Vue3 调度器思路的简化版”来看:

ts
const queue = []
const queuedJobs = new Set()
let isFlushing = false

/**
 * queueJob
 * 功能:把组件更新任务放进调度队列。
 * 核心职责:
 * 1. 对同一个 job 做去重
 * 2. 在本轮微任务中安排统一刷新
 * 参数:
 * - job: 组件更新任务,通常就是 instance.update
 * 返回值:
 * - 无显式返回值,副作用是维护调度队列
 */
function queueJob(job) {
  if (queuedJobs.has(job)) return
  queuedJobs.add(job)
  queue.push(job)

  if (!isFlushing) {
    isFlushing = true
    Promise.resolve().then(flushJobs)
  }
}

function flushJobs() {
  try {
    for (const job of queue) {
      job()
    }
  } finally {
    queue.length = 0
    queuedJobs.clear()
    isFlushing = false
  }
}

这段代码的重点不是细节一字不差,而是帮你建立下面这层直觉:Vue3 不是谁变了就立刻同步重渲染,而是把“该更新谁”先收起来,再统一执行。

4.4.2 一张图看清“状态变化到调度刷新”

mermaid
flowchart TD
    A[响应式属性被修改] --> B[trigger 找到依赖 effect]
    B --> C{effect 是否带 scheduler}
    C -- 否 --> D[直接执行 effect]
    C -- 是 --> E[queueJob 放入更新队列]
    E --> F[队列去重]
    F --> G[微任务阶段 flushJobs]
    G --> H[执行 instance.update]
    H --> I[重新 render + patch]

4.5 Vue3 的渲染流程是怎样的

如果把 Vue3 的渲染流程展开成一条完整主线,大致可以这样理解:

  1. .vue 文件先经过编译链路
  2. 模板被编译成渲染函数
  3. 组件初始化时执行 setup
  4. 渲染函数运行,生成虚拟 DOM
  5. 首次挂载时把虚拟 DOM 转成真实 DOM
  6. 响应式状态变化后,触发组件更新
  7. 重新执行渲染逻辑得到新的虚拟 DOM
  8. 新旧虚拟 DOM 做对比并 patch

也就是说,Vue3 的关键不是“直接改 HTML”,而是:响应式状态驱动渲染函数重新执行,再由运行时完成 vnode diff 和 DOM patch。

如果再往源码感受上靠一步,可以把组件更新理解成下面这种非常简化的链路:

ts
function componentUpdateFn() {
  // 新一轮渲染拿到最新 vnode
  const nextTree = renderComponentRoot(instance)
  const prevTree = instance.subTree
  instance.subTree = nextTree
  // 只把前后差异 patch 到页面
  patch(prevTree, nextTree)
}

这段代码想表达的只有一件事:响应式系统负责把更新机会送到组件,运行时负责把前后两棵 vnode 树的差异真正落到 DOM。

4.5.1 组件首次挂载和后续更新其实是两条分支

Vue3 的组件渲染虽然可以概括成“render -> vnode -> patch”,但在运行时里,首次挂载和后续更新其实不是完全一样的路径。

更贴近实现的理解是:

  1. 首次挂载时,要先创建组件实例、执行 setup、建立渲染副作用,再把子树挂到页面
  2. 后续更新时,重点变成“拿新旧子树做 patch”

可以把这条线看成下面这种简化结构:

ts
/**
 * setupRenderEffect
 * 功能:为组件建立渲染副作用,统一处理首次挂载和后续更新。
 * 核心职责:
 * 1. 首次执行时完成 mount
 * 2. 后续依赖变化时进入 update
 * 参数:
 * - instance: 当前组件实例
 * - container: 当前组件将被挂载到的 DOM 容器
 * 返回值:
 * - 无显式返回值,副作用是把组件渲染结果同步到页面
 */
function setupRenderEffect(instance, container) {
  instance.update = effect(
    () => {
      if (!instance.isMounted) {
        const subTree = renderComponentRoot(instance)
        patch(null, subTree, container)
        instance.subTree = subTree
        instance.isMounted = true
      } else {
        const nextTree = renderComponentRoot(instance)
        const prevTree = instance.subTree
        instance.subTree = nextTree
        patch(prevTree, nextTree, container)
      }
    },
    {
      scheduler: () => queueJob(instance.update)
    }
  )
}

这段代码最值得看懂的是:

  1. 组件更新函数本身就是一个 effect
  2. 首次执行做的是 mount
  3. 再次执行做的是 update
  4. 两次都会经过 renderComponentRoot
  5. 真正落地 DOM 的动作都在 patch

4.5.2 一张图把运行时阶段串起来

mermaid
flowchart TD
    A[createApp 挂载根组件] --> B[创建根 vnode]
    B --> C[patch 进入组件分支]
    C --> D[创建组件实例]
    D --> E[执行 setup]
    E --> F[建立 render effect]
    F --> G{是否首次挂载}
    G -- 是 --> H[renderComponentRoot 生成 subTree]
    H --> I[patch null 与 subTree]
    I --> J[生成真实 DOM]
    G -- 否 --> K[renderComponentRoot 生成 nextTree]
    K --> L[patch prevTree 与 nextTree]
    L --> M[按差异更新 DOM]

4.6 一张流程图先建立整体认知

mermaid
flowchart TD
    A[ref reactive 创建状态] --> B[组件渲染读取状态]
    B --> C[track 收集 effect]
    D[状态被修改] --> E[trigger 通知依赖]
    E --> F[scheduler 调度更新]
    F --> G[重新执行组件渲染 effect]
    G --> H[生成新 vnode]
    H --> I[diff + patch]
    I --> J[DOM 更新]

4.7 v-model 在 Vue3 里本质是什么

Vue3 里的 v-model 也还是语法糖。

从本质上看,它仍然可以理解成:

  1. 一个值绑定
  2. 一个更新事件

在组件场景里,最常见的理解方式是:

vue
<Child :modelValue="keyword" @update:modelValue="keyword = $event" />

所以它并不是一套和响应式系统平行存在的“魔法双向绑定机制”,而是:把状态下发和事件回传这两步封装成了更统一的写法。

如果把组件场景下的 v-model 也画成流程,可以这样看:

mermaid
flowchart LR
    A[父组件 keyword] --> B[:modelValue 传给子组件]
    B --> C[子组件内部输入框显示当前值]
    D[用户输入] --> E[子组件触发 update:modelValue]
    E --> F[父组件收到事件]
    F --> G[keyword = event]
    G --> H[父组件重新渲染]
    H --> B

4.8 Vue3 的设计模式可以怎么理解

这部分也不建议背成术语表,但值得建立工程直觉。

4.8.1 MVVM

Vue3 仍然可以放在 MVVM 语境里理解:

  1. Model:状态
  2. View:模板和 DOM
  3. ViewModel:组件实例和运行时桥接层

它的价值仍然是:开发者主要描述状态和视图关系,而不是手动写 DOM 更新。

4.8.2 观察者式依赖通知

Vue3 的 track / trigger / effect 明显带有观察者式设计特征:

  1. 依赖建立关系
  2. 数据变化时发出通知
  3. 相关副作用重新执行

实际写项目时,更多是这样,它不是只在背“观察者模式”这个词,而是在用一套:依赖收集 + 变更通知 + 副作用重跑

的机制组织响应式更新。

4.8.3 代理模式

Vue3 相比 Vue2,更明显的一层设计模式色彩就是代理思路。

因为 Proxy 本身就很接近:通过代理对象接管原对象的访问与修改。

这也是为什么 Vue3 能更自然地拦截读取、写入、删除等操作。

4.8.4 组件组合模式

Vue3 引入 Composition API 后,组件设计也更明显地往“组合”方向发展。

也就是:

  1. 用组件组合页面
  2. 用组合函数组合逻辑

这让它在复杂应用里更适合按业务关注点拆分能力。

5. Composition API 在解决什么问题

很多人第一次看到 Composition API,会把它理解成:只是换了一套写法。

这不准确。

它真正解决的是:复杂组件里,同一块业务逻辑会分散在 data、methods、computed、watch、生命周期等多个位置,后期很难维护和复用。

5.1 一个简单示例

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

const keyword = ref('')
const list = ref([
  { id: 1, name: 'Vue3' },
  { id: 2, name: 'Pinia' }
])

const filteredList = computed(() =>
  list.value.filter(item => item.name.includes(keyword.value))
)
</script>

<template>
  <section>
    <input v-model="keyword" placeholder="请输入关键字" />
    <ul>
      <li v-for="item in filteredList" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </section>
</template>

这里最重要的不是语法本身,而是:

  1. 状态
  2. 派生值
  3. 相关逻辑

可以自然放在同一块。

6. refreactive 怎么理解

这是 Vue3 里很高频的一组概念。

6.1 ref

ref 更适合:

  1. 单值状态
  2. 基本类型
  3. 需要显式引用包装的场景
ts
const count = ref(0)

6.2 reactive

reactive 更适合:

  1. 对象整体状态
  2. 多字段一起组织的状态块
ts
const form = reactive({
  name: '',
  age: 0
})

6.3 更稳的理解方式

不要死背“哪个一定更高级”,更实用的判断是:

  1. 单值、小块状态常用 ref
  2. 对象整体建模常用 reactive
  3. 实际项目里二者经常一起使用

7. computedwatchwatchEffect 的边界

7.1 computed

适合:

  1. 派生状态
  2. 纯计算结果
  3. 有缓存的值

7.2 watch

适合:

  1. 监听变化后执行副作用
  2. 请求接口
  3. 同步外部系统

7.3 watchEffect

可以看成 自动收集依赖的一类副作用执行方式

它更适合:

  1. 依赖关系比较直接
  2. 想快速建立副作用联动

但要注意:依赖太复杂时,watchEffect 也可能让触发边界变得不直观。

8. setupscript setup 是什么

8.1 setup

setup 是 Composition API 的入口。

它的意义不是“换个位置写代码”,而是:把组件逻辑组织从以 this 为中心,转向以显式变量和函数组合为中心。

8.2 script setup

script setup 可以看成 Vue3 为单文件组件提供的一种更精简、更适合现代开发的写法

它的价值在于:

  1. 模板里可以更自然使用脚本变量
  2. 样板代码更少
  3. 类型推导更顺

9. Vue3 为什么对 TypeScript 更友好

不是因为“Vue3 才能用 TS”,而是因为:

  1. API 更函数化
  2. 返回值和参数边界更显式
  3. setup、组合函数、defineProps 更利于类型推导

这让 Vue3 在大型项目里,尤其在:

  1. 组件 Props 约束
  2. 组合函数复用
  3. 状态管理

这些场景下会更顺手。

10. Vue3 的编译优化在说什么

很多人会笼统说 Vue3 更快,但实际写项目时,更多是这样:Vue3 不只是运行时变了,编译器也更聪明了。

它会在编译阶段做更多静态分析,例如:

  1. 标记动态节点
  2. 静态提升
  3. 减少不必要的比较和更新

所以 Vue3 的优化不是单点的,而是:

  1. 响应式系统
  2. 编译阶段
  3. 运行时更新策略

一起在升级。

10.1 编译阶段到底帮运行时做了什么

如果把 Vue3 的性能优化只理解成运行时更快,会漏掉一半关键点。

编译阶段真正做的事,可以压成下面 4 步:

  1. 解析模板,知道节点结构长什么样
  2. 识别哪些内容是静态的,哪些是动态的
  3. 给动态节点打上标记,例如 Patch Flag
  4. 生成更利于运行时快速更新的渲染函数

也就是说,编译器不是单纯“把模板翻译成 JS”,而是在提前告诉运行时:这里哪些地方将来可能变化,哪些地方可以放心跳过。

10.2 一个简化例子看 Patch Flag 和静态提升

例如模板:

vue
<div class="card">
  <h1>{{ title }}</h1>
  <p>固定说明文本</p>
</div>

编译后可以非常粗略地理解成下面这种风格:

ts
/**
 * hoistedNode
 * 功能:把完全静态的节点提升到渲染函数外部复用。
 * 作用:
 * - 避免每次组件更新都重新创建完全相同的 vnode
 */
const hoistedNode = createElementVNode('p', null, '固定说明文本', -1)

/**
 * render
 * 功能:根据当前状态生成组件 vnode 树。
 * 参数:
 * - _ctx: 当前组件渲染上下文
 * 返回值:
 * - 当前组件这一轮渲染得到的 vnode
 */
function render(_ctx) {
  return createElementVNode('div', { class: 'card' }, [
    createElementVNode('h1', null, _ctx.title, 1),
    hoistedNode
  ])
}

这段代码里最值得抓住的是:

  1. 静态的 <p> 可以被提升出去复用
  2. title 是动态的,所以对应节点会带动态标记
  3. 运行时更新时,就不用把整棵树都当成完全未知来处理

10.3 一张图看清“编译优化如何衔接运行时”

mermaid
flowchart LR
    A[template] --> B[compiler 解析 AST]
    B --> C[识别静态节点与动态节点]
    C --> D[静态提升 Hoist Static]
    C --> E[动态节点打 Patch Flag]
    D --> F[生成 render 函数]
    E --> F
    F --> G[运行时执行 render]
    G --> H[patch 时更聚焦地比较和更新]

11. Vue3 的新能力和生态方向

Vue3 时代更常见的一些能力包括:

  1. 更完整的组合式 API
  2. Teleport
  3. Suspense
  4. 多根节点支持
  5. 更自然地配合 Pinia
  6. 更现代的构建链路

这些能力说明的是:Vue3 更强调现代前端应用的工程边界,而不只是模板层语法。

12. Vue3 更适合哪些场景

Vue3 更适合:

  1. 新项目
  2. 复杂业务页面
  3. 逻辑复用需求较多的项目
  4. 对 TypeScript 体验有要求的团队
  5. 需要长期演进的中大型前端项目

13. 一个更贴近工程的组合函数示例

ts
import { computed, ref, watch } from 'vue'

export function useUserSearch(fetchUsers: (keyword: string) => Promise<string[]>) {
  const keyword = ref('')
  const list = ref<string[]>([])
  const loading = ref(false)

  const total = computed(() => list.value.length)

  watch(keyword, async (value) => {
    loading.value = true
    list.value = await fetchUsers(value)
    loading.value = false
  })

  return {
    keyword,
    list,
    total,
    loading
  }
}

这段代码想表达的重点是:

  1. 状态、派生值、副作用可以聚合成一块逻辑
  2. 这块逻辑可以在多个组件中复用
  3. 这是 Vue3 很重要的一条能力线

14. 工程里最容易踩的坑

14.1 把 Composition API 当成“写法升级”

最后还是会用旧思路写出新的大组件。

14.2 滥用 watch

本该用 computed 的地方用副作用处理,状态流会变乱。

14.3 refreactive 混用没有边界

会让状态组织越来越不清晰。

14.4 只会说 Vue3 更快,不会解释为什么

这会让对版本升级的理解停留在口号层。

15. 小结

如果把 Vue3 压缩成一句话,可以记住:Vue3 是一轮围绕响应式系统、逻辑组织方式、类型体验和工程化边界展开的系统升级,它更适合现代前端应用,也更适合作为新项目的默认主线。

继续往下读,比较自然的下一步是进入 Vue2 与 Vue3 对比Vuex 与 PiniaVue Router

基于 VitePress 构建的个人技术笔记。