Vue.js: Vue 3.4 新特性
最后更新:2026-08-26
Vue 3.4+(2023-12 发布)带来了多项革命性改进:defineModel 宏简化 v-model、响应式 props 解构、useTemplateRef 组合式 API、useId 生成唯一 ID。这些特性大幅简化模板代码,提升开发体验。
本课帮你掌握 5 大核心新特性,让你的 Vue 3 代码更简洁、更现代化。
1. 你将学到
defineModel宏替代 v-model 复杂写法- 响应式 props 解构(默认深响应)
useId生成 SSR 安全的唯一 IDuseTemplateRef组合式 ref- 过渡动画改进
- Vue 3.4 vs Vue 3.3 vs Vue 3.2 关键差异
- 5 大升级路径建议
2. 一个 v-model 组件的"父子同步"难题
(1) 痛点:5 行 props + 5 行 emit = 10 行 boilerplate
Alice 构建了一个带 v-model 的自定义输入组件:
JS
// ❌ 翻车版:v-model 需要 10 行样板代码
const props = defineProps({ modelValue: String })
const emit = defineEmits(['update:modelValue'])
const internalValue = ref(props.modelValue)
watch(() => props.modelValue, (val) => {
internalValue.value = val
})
watch(internalValue, (val) => {
emit('update:modelValue', val)
})
10 行样板代码,每个 v-model 组件都要写。
技术负责人:
"Alice,这太啰嗦了。Vue 3.4 的
defineModel一行就能搞定。"
(2) Vue 3.4 defineModel 宏解法
JS
// ✅ 正确版:1 行搞定 v-model
const modelValue = defineModel('modelValue')
// 现在可以:
// - 读:modelValue.value
// - 写:modelValue.value = 'new value'(自动 emit)
// - watch:watch(modelValue, ...)
// - 父组件 v-model 双向绑定照常工作
10 行 → 1 行。5 个 v-model 组件省 50 行代码。
(3) 收益
升级到 defineModel 后:
- 代码量:10 行 → 1 行(-90%)
- 可读性:1 行看清意图
- 类型推断:TypeScript 自动推断
- 可扩展:支持 transform 转换、localStorage 等
3. defineModel 宏详解
(1) 基本用法
VUE
<!-- 子组件:CustomInput.vue -->
<template>
<input :value="modelValue" @input="modelValue = $event.target.value">
</template>
<script setup>
// ✅ 1 行 v-model
const modelValue = defineModel('modelValue')
</script>
VUE
<!-- 父组件:使用 v-model -->
<template>
<CustomInput v-model="searchQuery" />
<p>Search: {{ searchQuery }}</p>
</template>
<script setup>
const { ref } = Vue
const searchQuery = ref('')
</script>
(2) 5 大高级用法
JS
// 1. 默认值
const modelValue = defineModel('modelValue', { default: '' })
// 2. 类型约束(TypeScript)
const count = defineModel<number>('count', { default: 0 })
// 3. transform 转换(写时转换)
const trimmed = defineModel('text', {
set(value) {
return value.trim() // 自动 trim
}
})
// 4. getter(读时转换)
const upper = defineModel('text', {
get(value) {
return value.toUpperCase() // 总是大写
}
})
// 5. 多个 v-model
const name = defineModel('name')
const email = defineModel('email')
const phone = defineModel('phone')
(3) 完整示例
VUE
<!-- 子组件:TrimInput.vue -->
<template>
<input :value="value" @input="value = $event.target.value">
</template>
<script setup>
const value = defineModel('value', {
// get: 读时转换
get(val) {
return val?.toUpperCase() || ''
},
// set: 写时转换
set(val) {
return val?.trim() || ''
},
default: ''
})
</script>
VUE
<!-- 父组件 -->
<CustomInput v-model="name" />
<!-- 用户输入 "alice" → trim 不变 → uppercase → "ALICE" -->
4. 响应式 props 解构
(1) Vue 3.4 之前的痛点
JS
// ❌ Vue 3.3 及之前
const props = defineProps({ count: Number, name: String })
// 模板中直接用 props.count
// JS 中要 props.count
// 解构会丢失响应
const { count, name } = props // count/name 变普通值
(2) Vue 3.4 解构保持响应
VUE
<!-- 子组件 -->
<template>
<p>{{ count }} - {{ name }}</p>
<button @click="count++">+1</button>
</template>
<script setup>
// ✅ Vue 3.4:解构后仍是响应式
const { count, name } = defineProps<{
count: number
name: string
}>()
</script>
解构的 props 保持响应式!这是 Vue 3.4 最大的改进之一。
(3) 5 大应用场景
JS
// 1. 简化 props 使用
const { count, name } = defineProps<{ count: number; name: string }>()
// 2. 解构 + 重命名
const { count: total, name: userName } = defineProps<{ count: number; name: string }>()
// 3. 解构 + 默认值
const { count = 0, name = 'Guest' } = defineProps<{ count?: number; name?: string }>()
// 4. 配合 watch(解构后 watch 仍工作)
const { count } = defineProps<{ count: number }>()
watch(count, (newVal) => console.log('count changed:', newVal))
// 5. 配合 computed(同样保持响应)
const { count } = defineProps<{ count: number }>()
const double = computed(() => count * 2)
(4) 注意事项
JS
// ⚠️ 解构的 props 不能再赋值
const { count } = defineProps<{ count: number }>()
count = 10 // ❌ 错误:Assignment to constant variable
// ✅ 正确:通过 emit 让父组件改
const emit = defineEmits(['update:count'])
const handleClick = () => emit('update:count', count + 1)
5. useId 唯一 ID
(1) 为什么需要 useId?
SSR(服务端渲染)时,手写 ID 会冲突(多个组件实例用同一个 ID)。useId 生成全局唯一且 SSR 安全的 ID。
JS
// ❌ 旧写法:手写 ID,SSR 会冲突
const id = `input-${Math.random()}`
// ✅ Vue 3.4+:useId
const { useId } = Vue
const id = useId() // 'v-0', 'v-1', ...
(2) 5 大使用场景
VUE
<!-- 1. 表单 label 关联 -->
<template>
<label :for="id">Username</label>
<input :id="id" v-model="username">
</template>
<script setup>
const { useId } = Vue
const id = useId()
const username = ref('')
</script>
<!-- 2. ARIA 属性 -->
<template>
<button :aria-describedby="`${id}-help`">Click</button>
<p :id="`${id}-help`">This button does X</p>
</template>
<!-- 3. 表单错误信息 -->
<template>
<input v-model="email" :aria-invalid="!!error" :aria-errormessage="`${id}-error`">
<p v-if="error" :id="`${id}-error`">{{ error }}</p>
</template>
<!-- 4. Tooltip 关联 -->
<template>
<button :aria-describedby="`${id}-tip`">?</button>
<span :id="`${id}-tip`" class="tooltip">Help text</span>
</template>
<!-- 5. 模态框 -->
<template>
<div :id="`${id}-modal`" role="dialog" aria-modal="true">
...
</div>
</template>
(3) 5 大优势
| 优势 | 说明 |
|---|---|
| SSR 安全 | 服务端/客户端 ID 一致 |
| 唯一 | 同页面多个实例不冲突 |
| 稳定 | 组件 re-render 不变 |
| 可读 | ID 自带 v- 前缀易识别 |
| 轻量 | 0 依赖(Vue 内置) |
6. useTemplateRef(Vue 3.5+ 详解)
(1) 完整特性
VUE
<template>
<input ref="usernameInput">
<MyChart ref="chartComponent" :data="chartData" />
</template>
<script setup lang="ts">
const { useTemplateRef, onMounted } = Vue
import MyChart from './MyChart.vue'
// ✅ Vue 3.5+:一个名字搞定
const inputRef = useTemplateRef<HTMLInputElement>('usernameInput')
const chartRef = useTemplateRef<InstanceType<typeof MyChart>>('chartComponent')
// ✅ TypeScript 完美推断
onMounted(() => {
inputRef.value?.focus() // HTMLInputElement 类型
chartRef.value?.refresh() // 组件实例方法
})
</script>
(2) Vue 3.5 vs Vue 3.4 对比
| 维度 | Vue 3.4 变量 ref | Vue 3.5 useTemplateRef |
|---|---|---|
| 模板 ref | <input ref="inputRef"> |
<input ref="usernameInput"> |
| JS ref | const inputRef = ref(null) |
const inputRef = useTemplateRef('usernameInput') |
| 类型推断 | 需手动 ref<HTMLInputElement | null> |
自动推断 |
| 名字一致性 | 可能拼错不报错 | 不一致会报错 |
| 简洁度 | 2 行 | 1 行 |
7. 5 大升级路径建议
(1) 升级时间
| Vue 版本 | 发布时间 | 状态 |
|---|---|---|
| Vue 3.0 | 2020-09 | 旧(建议升级) |
| Vue 3.2 | 2021-08 | 稳定 |
| Vue 3.3 | 2023-05 | 稳定 |
| Vue 3.4 | 2023-12 | 当前推荐 |
| Vue 3.5 | 2024-09 | 最新稳定 |
(2) 升级清单
TEXT
📖 仅展示
✅ Vue 3.4:
- defineModel 宏替代 v-model 复杂写法
- 响应式 props 解构
- 同名简写(<input :value> 替代 :value="value")
- 错误处理改进
- 模板解析器重写(2x 性能)
✅ Vue 3.5:
- useTemplateRef(推荐)
- 响应式 props 解构改进
- useId 稳定
- 延迟 teleport
- 改进的 Suspense
(3) 升级建议
| 场景 | 建议 |
|---|---|
| 新项目 | 直接用 Vue 3.5+ |
| 老项目(Vue 3.3-) | 升级到 3.5,按特性迁移 |
| 老项目(Vue 3.0-3.2) | 升级到 3.3,再升 3.5 |
| 超大型项目 | 渐进式升级(先升级 Vue,再迁移 v-model 等) |
8. 完整示例:Vue 3.4+ 综合应用
▶ 示例:v-model 双向绑定(CDN Vue 3.5 支持 :value + @input 简化)
HTML
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<style>
input { padding: 6px; border: 1px solid #ddd; border-radius: 4px; margin: 4px; }
.btn { padding: 6px 12px; background: #42b883; color: white; border: none; border-radius: 4px; cursor: pointer; }
</style>
<div id="app">
<h4>1. v-model 双向绑定(CDN Vue 3.5)</h4>
<input v-model="text" placeholder="输入文本">
<p>实时显示: {{ text || '(空)' }}</p>
<h4>2. 表单验证 + 错误提示</h4>
<input v-model="email" placeholder="Email">
<p v-if="error" style="color: #ef4444;">{{ error }}</p>
<button class="btn" @click="validate">验证</button>
</div>
<script>
const { createApp, ref, computed } = Vue
const App = {
setup() {
const text = ref('')
const email = ref('')
const error = ref('')
function validate() {
if (!email.value || !email.value.includes('@')) {
error.value = '❌ 邮箱格式错误'
} else {
error.value = ''
alert('✅ 邮箱有效')
}
}
return { text, email, error, validate }
}
}
createApp(App).mount('#app')
</script>
▶ 示例:Vue 3.4+ 新特性速查(⚠️ 需 SFC + Vite,CDN 不可用)
⚠️ 以下示例需在 Vite +
<script setup> 项目中使用,CDN Vue 3 全局构建不暴露 defineModel/useId/useTemplateRef 等编译宏。这些宏在 Vite 编译时被替换为运行时函数。
VUE
<!-- ⚠️ 需 Vite 项目,纯展示 -->
<!-- 1. defineModel 宏(替代 v-model 复杂写法) -->
<script setup>
// 1 行替代 10 行:props.modelValue + emit('update:modelValue')
const text = defineModel('text', { default: '' })
</script>
<template>
<input v-model="text">
</template>
<!-- 2. 响应式 props 解构(Vue 3.4+) -->
<script setup>
// ✅ 解构后仍响应(默认深响应)
const { count, name } = defineProps<{
count: number
name: string
}>()
// 直接用 count, name,无需 props.count, props.name
</script>
<!-- 3. useId(SSR 安全的唯一 ID) -->
<script setup>
const { useId } = Vue
const id = useId() // 自动生成 v-0, v-1, ...
</script>
<template>
<label :for="id">Email</label>
<input :id="id" type="email">
</template>
<!-- 4. useTemplateRef(Vue 3.5+ 强类型) -->
<script setup lang="ts">
const { useTemplateRef, onMounted } = Vue
// TypeScript 自动推断为 HTMLInputElement | null
const inputRef = useTemplateRef<HTMLInputElement>('usernameInput')
onMounted(() => {
inputRef.value?.focus()
})
</script>
<template>
<input ref="usernameInput" type="text">
</template>
▶ 示例:5 大常见错误速查
| 错误 | 现象 | 解决 |
|---|---|---|
| defineModel 解构 | 失去响应 | 直接用 const modelValue = defineModel() |
| props 解构后赋值 | 报错 | 用 emit 让父改 |
| useId 写固定字符串 | SSR 冲突 | 改用 useId() |
| useTemplateRef 名字错 | ref.value 是 null | 名字必须匹配 |
| 升级 Vue 3.4+ 改太多 | 兼容性问题 | 渐进式迁移 |
▶ 示例:5 大 Vue 3.4+ 升级收益
| 收益 | 数据 |
|---|---|
| 代码量减少 | 30-50%(defineModel 简化) |
| 性能提升 | 模板解析 2x 更快 |
| 类型推断 | 100% 准确(TypeScript) |
| SSR 友好 | useId 解决 ID 冲突 |
| 开发者体验 | DX 显著提升 |
❓ 常见问题
Q defineModel 替代 v-model 完整写法吗?
A 是的。
defineModel('modelValue') 等同于 props.modelValue + emit('update:modelValue') 的 10 行代码。完全向后兼容。Q 响应式 props 解构什么时候支持?
A Vue 3.4+(2023-12)。Vue 3.3 及以下不支持。解构后 props 仍响应(默认深响应)。
Q useId 每次渲染会变吗?
A 不会。useId 生成的 ID 在组件生命周期内保持稳定(SSR 和客户端一致)。
Q useTemplateRef vs ref 变量?
A useTemplateRef 是 Vue 3.5+ 引入的,TypeScript 推断更强,重命名更安全。普通项目用 ref 变量即可。
Q defineProps 解构能用于 watch 吗?
A 能。
const { count } = defineProps() + watch(count, ...) 完全 work。Q 如何从 Vue 3.3 升级到 3.4+?
A
npm update vue@latest。代码层:defineModel 替代 v-model 复杂写法;响应式解构替代 props.xxx。零破坏。Q Vue 3.5+ 对 TypeScript 有什么改进?
A defineProps 响应式解构 + useTemplateRef 完美类型推断 + 宏的 TS 支持更完善。推荐用
<script setup lang="ts">。📖 小节
- defineModel 宏:1 行 v-model,替代 10 行 boilerplate
- 响应式 props 解构:Vue 3.4+ 默认深响应
- useId:SSR 安全的唯一 ID
- useTemplateRef:Vue 3.5+ 组合式 ref,强类型推断
- Vue 3.4 升级:defineModel + 响应式解构 + 同名简写
- Vue 3.5 升级:useTemplateRef + useId 稳定
- 推荐:直接用 Vue 3.5+
📝 作业
-
基础题(难度⭐) 用 defineModel 重构一个 v-model 组件:
- 子组件 CustomInput.vue:1 行 defineModel
- 父组件:v-model 双向绑定
- 支持默认值
-
进阶题(难度⭐⭐) 用 Vue 3.4+ 响应式解构重构组件:
- 解构 count, name 等 props
- 配合 watch + computed
- 测试响应式保持
-
挑战题(难度⭐⭐⭐) 实现完整的 Vue 3.4+ 升级示例:
- 5 个组件用 defineModel 替代 v-model 复杂写法
- 3 个组件用响应式 props 解构
- 5 个组件用 useId(label/input 关联)
- 3 个组件用 useTemplateRef(Vue 3.5+)
- 5 个 TypeScript 强类型组件
- 测试 SSR 场景下 useId 的一致性