为啥升Vue3 有啥优势?
Vue2 Vue3 对比
Vue3 通过 Proxy 响应式、Composition API、完整 TS 能力、编译器优化 与 新内置能力(Teleport/Suspense/Fragments) ,系统性解决了 Vue2 在大型项目可维护性、类型安全、性能与可复用性上的天花板问题。
开发者需关注的特性
RFC 机制
简单说:ref 就是让一个值变成「响应式引用」。
import { ref } from 'vue'
const count = ref(0)
console.log(count.value) // 0
count.value++
console.log(count.value) // 1
-
ref(0)创建了一个响应式对象,结构为{ value: 0 }; -
在模板中,会自动“解包”,不用
.value:
<template>
<div>{{ count }}</div> <!-- 会自动取 count.value -->
<button @click="count++">+1</button>
</template>
常见使用场景
定义基础状态
const title = ref('Hello Vue3')
const visible = ref(false)
DOM 引用(替代 Vue2 的 $refs)
<template>
<input ref="inputRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
</script>
组合逻辑(Composables)
// useCounter.js
import { ref } from 'vue'
export function useCounter() {
const count = ref(0)
const inc = () => count.value++
const dec = () => count.value--
return { count, inc, dec }
}
// 组件中使用
const { count, inc, dec } = useCounter()
ref 的高级技巧
✅ 1. 与对象绑定
const person = ref({ name: 'Tom', age: 18 })
person.value.age++ // 响应式更新
✅ 2. 响应式计算
import { ref, computed } from 'vue'
const count = ref(2)
const double = computed(() => count.value * 2)
✅ 3. 监听 ref
import { watch } from 'vue'
watch(count, (newVal, oldVal) => {
console.log('变化:', oldVal, '→', newVal)
})
核心原理简述
Vue3 内部定义大致如下(简化):
function ref(value) {
return reactive({ value })
}
- Vue 自动通过 Proxy 监听
.value; - 模板中自动
.value解包; - 保持了原始值响应式 + 对象响应式统一接口。
响应式系统
Vue2 在初始化数据时,会通过 Object.defineProperty 把每个属性“拦截”成带 getter/setter 的形式。
组件初始化 → 遍历 data → defineProperty 劫持每个 key
→ getter 收集 watcher
→ setter 触发 watcher.update()
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log('get', key)
return val
},
set(newVal) {
console.log('set', key, newVal)
val = newVal
}
})
}
const data = {}
defineReactive(data, 'count', 0)
data.count // get count
data.count = 1 // set count 1
Vue3 改用 Proxy,一层代理整个对象,无需遍历所有属性。
组件初始化 → reactive 创建 Proxy
→ getter(track) 收集依赖
→ setter(trigger) 触发副作用
const data = { count: 0 }
const p = new Proxy(data, {
get(target, key, receiver) {
console.log('get', key)
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
console.log('set', key, value)
const res = Reflect.set(target, key, value, receiver)
// 触发依赖更新
return res
}
})
p.count++ // get + set
Typescript 支持
Vue3 更加方便 早期发现问题成本会降低很多
Vue2(Class/装饰器)
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class UserCard extends Vue {
@Prop({ type: String, required: true }) readonly name!: string
count = 0
mounted() { /* this.name 类型常OK,但混用mixin易错 */ }
}
Vue3(<script setup> 推荐)
<script setup lang="ts">
const props = defineProps<{ name: string }>()
const emit = defineEmits<{
(e: 'update:count', v: number): void
}>()
import { ref } from 'vue'
const count = ref(0)
function inc() { count.value++; emit('update:count', count.value) }
</script>
Options API VS Composition API / <script setup>
Vue2(Options API)分开写一块一块的功能
<!-- UserCard.vue -->
<template>
<div>
<h3>{{ title }}</h3>
<p>{{ fullName }}</p>
<input v-model="keyword" @keyup.enter="search" />
<button @click="inc">{{ count }}</button>
</div>
</template>
<script>
export default {
props: { first: String, last: String },
data() {
return { title: 'User', count: 0, keyword: '' }
},
computed: {
fullName() { return `${this.first} ${this.last}` }
},
watch: {
keyword(nv) { console.log('kw:', nv) }
},
methods: {
inc() { this.count++ },
search() { this.$emit('search', this.keyword) }
},
created() { console.log('created') },
mounted() { console.log('mounted') }
}
</script>
但 Options API 的写法也有几个很严重的问题: 由于所有数据都挂载在 this 之上,因而 Options API 的写法对 TypeScript 的类型推导很不友好,并且这样也不好做 Tree-shaking 清理代码。
新增功能基本都得修改 data、method 等配置,并且代码上 300 行之后,会经常上下反复横跳,开发很痛苦。
代码不好复用,Vue 2 的组件很难抽离通用逻辑,只能使用 mixin,还会带来命名冲突的问题。
Vue3(Composition API,<script setup> 推荐)
<!-- UserCard.vue -->
<template>
<div>
<h3>{{ title }}</h3>
<p>{{ fullName }}</p>
<input v-model="keyword" @keyup.enter="search" />
<button @click="inc">{{ count }}</button>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted, onBeforeMount } from 'vue'
const props = defineProps<{ first: string; last: string }>()
const emit = defineEmits<{ (e: 'search', kw: string): void }>()
const title = ref('User')
const count = ref(0)
const keyword = ref('')
const fullName = computed(() => `${props.first} ${props.last}`)
watch(keyword, (nv) => console.log('kw:', nv))
function inc() { count.value++ }
function search() { emit('search', keyword.value) }
onBeforeMount(() => console.log('created'))
onMounted(() => console.log('mounted'))
</script>
用到的功能都 import 进来,对 Tree-shaking 很友好,我的例子里没用到功能,打包的时候会被清理掉 ,减小包的大小。
不再上下反复横跳,我们可以把一个功能模块的 methods、data 都放在一起书写,维护更轻松。
代码方便复用,可以把一个功能所有的 methods、data 封装在一个独立的函数里,复用代码非常容易。
Composotion API 新增的 return 等语句,在实际项目中使用
新一代工程化工具 Vite
Webpack 是“打包优先” —— 一切先打包再运行;
Vite 是“原生模块优先” —— 借助浏览器原生 ES Module 实现“按需加载”,再用 Rollup 打包生产。
并非是 Vue3 专属,Vite 主要提升的是开发的体验,Webpack 等工程化工具的原理,就是根据你的 import 依赖逻辑,形成一个依赖图,然后调用对应的处理工具,把整个项目打包后,放在内存里再启动调试。
由于要预打包,所以复杂项目的开发,启动调试环境需要 3 分钟都很常见,Vite 就是为了解决这个时间资源的消耗问题出现的。
- Webpack(bundle-first)启动流程
[开发者运行 dev]
↓
[读取配置/插件/Loader]
↓
[构建完整依赖图]
↓
[打完整包(或多入口Chunk)]
↓
[启动 DevServer,提供内存中的 bundle]
↓
[浏览器加载单/多 bundle]
↓
[HMR:文件变更]
↓
[增量重新构建相关 chunk]
↓
[推送热更新补丁 → 替换模块/触发刷新]
-
Vite(esm-first)启动流程
[开发者运行 dev]
↓
[秒启本地 Dev Server]
↓
[依赖预构建(一次性,用 esbuild)]
↓
[按请求即时编译源码(如 .vue/.ts)]
↓
[浏览器通过 ESM 逐模块请求]
↓
[HMR:文件变更]
↓
[仅编译受影响模块]
↓
[精准替换该模块(ESM 热替换,无需重打包)]
解决了哪些问题(重要几点)
- 响应式缺陷(Vue2 的 getter/setter)
- 痛点:数组下标/length 变更、对象新增/删除属性不响应,需
Vue.set/delete;深层依赖追踪易漏报。 - Vue3:基于 Proxy 的响应式系统,天然支持属性新增/删除、数组操作,无需
set/delete,依赖追踪更准确,副作用更可控。
- 大型组件与逻辑复用困难(Options + mixins 冲突/命名污染)
- 痛点:mixin 命名碰撞、来源不清;复杂组件 methods/computed/data 四散,难以围绕“功能”聚合。
- Vue3:Composition API(
setup/ref/reactive/computed/watch)按“功能切片”组织代码,自定义 hooks(composables) 可复用且可测试、可类型推断,彻底替代大多数 mixins 场景。
- 类型与可维护性(TS 体验差)
- 痛点:Vue2 TS 需要装饰器/额外语法,推断不稳;事件/props 的类型约束不友好。
- Vue3:内置 TypeScript 一等公民,
defineComponent/emits/defineProps等让 props/emit 有完善的类型检查与 IDE 推断。
- 性能与包体(编译/运行双端优化不足)
- 痛点:VNode diff 粗粒度、静态节点重复计算,包不易摇树优化。
- Vue3:编译器引入 patchFlag/静态提升/事件缓存 等;运行时 更快的 VDOM;更好的 Tree-Shaking(按需打包核心模块),同等功能体积更小。
- 生态与未来
- 痛点:Vue2 生态逐渐维护最小化,新库偏向 Vue3。
- Vue3:新生态(如 Naive UI、VueUse、Volar)全面围绕 Vue3/TS 优化,长期演进保障。