成都商报app
73.15MB · 2025-10-27
Vue3 通过 Proxy 响应式、Composition API、完整 TS 能力、编译器优化 与 新内置能力(Teleport/Suspense/Fragments) ,系统性解决了 Vue2 在大型项目可维护性、类型安全、性能与可复用性上的天花板问题。
简单说: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)
$refs)<template>
<input ref="inputRef" />
</template>
<script setup>
import { ref, onMounted } from 'vue'
const inputRef = ref(null)
onMounted(() => {
inputRef.value.focus()
})
</script>
// 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 的高级技巧const person = ref({ name: 'Tom', age: 18 })
person.value.age++ // 响应式更新
import { ref, computed } from 'vue'
const count = ref(2)
const double = computed(() => count.value * 2)
import { watch } from 'vue'
watch(count, (newVal, oldVal) => {
console.log('变化:', oldVal, '→', newVal)
})
Vue3 内部定义大致如下(简化):
function ref(value) {
return reactive({ value })
}
.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
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>
<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 等语句,在实际项目中使用
Webpack 是“打包优先” —— 一切先打包再运行;
Vite 是“原生模块优先” —— 借助浏览器原生 ES Module 实现“按需加载”,再用 Rollup 打包生产。
并非是 Vue3 专属,Vite 主要提升的是开发的体验,Webpack 等工程化工具的原理,就是根据你的 import 依赖逻辑,形成一个依赖图,然后调用对应的处理工具,把整个项目打包后,放在内存里再启动调试。
由于要预打包,所以复杂项目的开发,启动调试环境需要 3 分钟都很常见,Vite 就是为了解决这个时间资源的消耗问题出现的。
[开发者运行 dev]
↓
[读取配置/插件/Loader]
↓
[构建完整依赖图]
↓
[打完整包(或多入口Chunk)]
↓
[启动 DevServer,提供内存中的 bundle]
↓
[浏览器加载单/多 bundle]
↓
[HMR:文件变更]
↓
[增量重新构建相关 chunk]
↓
[推送热更新补丁 → 替换模块/触发刷新]
Vite(esm-first)启动流程
[开发者运行 dev]
↓
[秒启本地 Dev Server]
↓
[依赖预构建(一次性,用 esbuild)]
↓
[按请求即时编译源码(如 .vue/.ts)]
↓
[浏览器通过 ESM 逐模块请求]
↓
[HMR:文件变更]
↓
[仅编译受影响模块]
↓
[精准替换该模块(ESM 热替换,无需重打包)]
Vue.set/delete;深层依赖追踪易漏报。set/delete,依赖追踪更准确,副作用更可控。setup/ref/reactive/computed/watch)按“功能切片”组织代码,自定义 hooks(composables) 可复用且可测试、可类型推断,彻底替代大多数 mixins 场景。defineComponent/emits/defineProps 等让 props/emit 有完善的类型检查与 IDE 推断。