引言

Vue 3.5作为Vue 3系列的最新版本,带来了许多令人兴奋的新特性和性能优化。本文将深入分析这些新特性的技术细节,并通过实际代码示例展示如何在项目中应用它们。

核心新特性概览

  1. 响应式系统优化 - 内存占用降低,性能提升
  2. Props解构 - 更简洁的API
  3. defineModel改进 - 简化双向绑定
  4. Suspense增强 - 更好的异步组件处理
  5. SSR改进 - 服务端渲染性能提升
  6. TypeScript支持 - 更好的类型推导

响应式系统优化

内存优化

Vue 3.5对响应式系统进行了底层重构,显著降低了内存占用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<script setup>
import { ref, reactive, computed } from 'vue'

// Vue 3.5: 内存占用优化
const items = ref(new Array(10000).fill(0).map((_, i) => ({
  id: i,
  name: `Item ${i}`,
  value: Math.random()
}))

// 计算属性缓存优化
const total = computed(() => {
  return items.value.reduce((sum, item) => sum + item.value, 0)
})

// 响应式Map/Set性能提升
const mapData = reactive(new Map())
const setData = reactive(new Set())
</script>

Proxy优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Vue 3.5的Proxy实现更加高效
const state = reactive({
  nested: {
    deeply: {
      value: 1
    }
  }
})

// 触发追踪优化
watchEffect(() => {
  console.log(state.nested.deeply.value) // 更少的依赖追踪开销
})

Props解构

基础用法

Vue 3.5中最具实用性的特性之一:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<script setup>
// Vue 3.5 之前
const props = defineProps<{
  user: { name: string; age: number }
  title: string
}>()

// Vue 3.5: 直接解构props
const { user, title } = defineProps<{
  user: { name: string; age: number }
  title: string
}>()

// 解构后仍然保持响应式
console.log(user.name) // 响应式
</script>

默认值和类型校验

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<script setup>
interface Props {
  title: string
  count?: number
  items?: string[]
  disabled?: boolean
}

// withDefaults + 解构
const {
  title,
  count = 0,
  items = () => [],
  disabled = false
} = defineProps<Props>()

// 完整类型推导
const total = computed(() => items.value.length + count)
</script>

响应式解构行为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<script setup>
// 解构的props保持响应式
const { user } = defineProps<{ user: { name: string } }>()

// ✅ 响应式更新
watchEffect(() => {
  console.log(user.name) // user.name变化时重新执行
})

// ✅ 直接修改响应
function updateName(newName: string) {
  // 注意:不能直接修改props,这只是一个演示
  // 实际应该emit事件
  console.log(user.name)
}
</script>

defineModel改进

简化双向绑定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script setup>
// Vue 3.5 之前
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{
  'update:modelValue': [value: string]
}>()

const value = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})

// Vue 3.5: 简化为defineModel
const model = defineModel<string>()

// 直接修改即可
function handleChange() {
  model.value = 'new value'
}
</script>

<template>
  <input v-model="model" />
</template>

多个v-model

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<script setup>
// Vue 3.5 支持多个命名model
const title = defineModel<string>('title')
const content = defineModel<string>('content')
const author = defineModel<{ name: string; email: string }>('author')

// 使用
function updateAll() {
  title.value = 'New Title'
  content.value = 'New Content'
  author.value = { name: 'John', email: 'john@example.com' }
}
</script>

<template>
  <div>
    <input v-model="title" />
    <textarea v-model="content" />
    <input v-model="author.name" />
    <input v-model="author.email" />
  </div>
</template>

自定义model选项

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<script setup>
// 带选项的defineModel
const count = defineModel<number>('count', {
  required: true,
  default: 0,
  // 自定义getter/setter
  get(value) {
    return value * 2
  },
  set(value) {
    return Math.floor(value / 2)
  }
})
</script>

Suspense增强

更好的错误处理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<template>
  <Suspense>
    <template #default>
      <AsyncComponent />
    </template>
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

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

const error = ref(null)

onErrorCaptured((err) => {
  error.value = err
  // 阻止错误继续传播
  return false
})
</script>

嵌套Suspense

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<template>
  <Suspense>
    <template #default>
      <ParentComponent />
    </template>
    <template #fallback>
      <div>Loading parent...</div>
    </template>
  </Suspense>
</template>

<script setup>
// ParentComponent.vue
import { Suspense } from 'vue'

// ParentComponent内部也可以使用Suspense
// Vue 3.5改进了嵌套Suspense的处理
</script>

SSR改进

服务端渲染性能

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 服务器端
import { renderToString } from 'vue/server-renderer'

// Vue 3.5: 更快的SSR渲染
const app = createSSRApp({
  data: () => ({ message: 'Hello SSR' }),
  template: `<div>{{ message }}</div>`
})

const html = await renderToString(app)

流式渲染增强

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { renderToStream } from 'vue/server-renderer'
import { createApp } from 'vue'

const app = createApp({
  template: `
    <div>
      <h1>{{ title }}</h1>
      <Suspense>
        <AsyncContent />
      </Suspense>
    </div>
  `,
  data() {
    return { title: 'Streaming SSR' }
  }
})

const stream = renderToStream(app)

return new Response(stream, {
  headers: {
    'Content-Type': 'text/html',
    'Transfer-Encoding': 'chunked'
  }
})

实战案例:构建高性能列表组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
<!-- SmartList.vue -->
<template>
  <div class="smart-list">
    <!-- 使用Props解构 -->
    <div v-if="loading">Loading...</div>

    <!-- 虚拟滚动优化 -->
    <RecycleScroller
      v-if="!loading && items.length"
      :items="paginatedItems"
      :item-size="50"
      key-field="id"
      v-slot="{ item }"
    >
      <ListItem
        :item="item"
        v-model:selected="selectedIds"
        @update:item="$emit('update:item', $event)"
      />
    </RecycleScroller>

    <!-- 分页控制 -->
    <Pagination
      v-model:page="currentPage"
      v-model:page-size="pageSize"
      :total="items.length"
    />
  </div>
</template>

<script setup lang="ts">
import { computed, watch } from 'vue'

interface Item {
  id: number
  name: string
  value: number
}

interface Props {
  items: Item[]
  loading?: boolean
  initialPageSize?: number
}

// Vue 3.5: Props解构
const {
  items,
  loading = false,
  initialPageSize = 20
} = defineProps<Props>()

// Vue 3.5: 简化的emit定义
const emit = defineEmits<{
  'update:selected': [ids: number[]]
}>()

// Vue 3.5: 简化的model
const selectedIds = defineModel<number[]>('selected', { default: () => [] })
const currentPage = defineModel<number>('page', { default: 1 })
const pageSize = defineModel<number>('pageSize', { default: initialPageSize })

// 计算属性优化
const paginatedItems = computed(() => {
  const start = (currentPage.value - 1) * pageSize.value
  const end = start + pageSize.value
  return items.slice(start, end)
})

const totalPages = computed(() => Math.ceil(items.length / pageSize.value))

// watch优化
watch(currentPage, (newPage) => {
  console.log(`Page changed to ${newPage}`)
})
</script>

<style scoped>
.smart-list {
  container-type: inline-size;
}

@container (max-width: 600px) {
  .smart-list {
    font-size: 14px;
  }
}
</style>

组合式API增强

useTemplateRef

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<script setup lang="ts">
import { useTemplateRef, onMounted } from 'vue'

// Vue 3.5新增: 类型安全的ref
const inputRef = useTemplateRef<HTMLInputElement>('input')

onMounted(() => {
  // 自动类型推导
  inputRef.value?.focus()
  console.log(inputRef.value?.value)
})
</script>

<template>
  <input ref="input" type="text" />
</template>

useId

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<script setup>
import { useId } from 'vue'

const labelId = useId()
const inputId = useId()
</script>

<template>
  <div>
    <label :id="labelId">Username</label>
    <input :id="inputId" :aria-labelledby="labelId" />
  </div>
</template>

性能优化技巧

1. 响应式优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<script setup>
import { shallowRef, triggerRef } from 'vue'

// 大对象使用shallowRef
const largeData = shallowRef(createLargeObject())

// 手动触发更新
function updateLargeData() {
  largeData.value = newData
  triggerRef(largeData)
}
</script>

2. 计算属性缓存

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<script setup>
import { computed } from 'vue'

const items = ref([])

// 避免在计算属性中创建新对象
const itemMap = computed(() => {
  const map = new Map()
  items.value.forEach(item => map.set(item.id, item))
  return map
})

// 缓存结果
const filtered = computed(() => {
  return items.value.filter(item => item.active)
})
</script>

3. 组件懒加载

1
2
3
4
5
6
7
<script setup>
import { defineAsyncComponent } from 'vue'

const HeavyComponent = defineAsyncComponent(() =>
  import('./HeavyComponent.vue')
)
</script>

迁移指南

从Vue 3.4升级

  1. 更新依赖
1
2
npm update vue@latest
npm update @vue/compiler-sfc@latest
  1. Props解构迁移
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- 之前 -->
<script setup>
const props = defineProps<Props>()
const { foo, bar } = toRefs(props)
</script>

<!-- 之后 -->
<script setup>
const { foo, bar } = defineProps<Props>()
</script>
  1. defineModel迁移
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!-- 之前 -->
<script setup>
const props = defineProps<{ modelValue: string }>()
const emit = defineEmits(['update:modelValue'])
const value = computed({
  get: () => props.modelValue,
  set: (val) => emit('update:modelValue', val)
})
</script>

<!-- 之后 -->
<script setup>
const value = defineModel<string>()
</script>

最佳实践

  1. 优先使用Props解构 - 代码更简洁
  2. 合理使用defineModel - 简化双向绑定
  3. 利用响应式优化 - 减少不必要的响应式开销
  4. 组件设计原则 - 单一职责、可复用性
  5. TypeScript友好 - 充分利用类型系统

总结

Vue 3.5带来了许多实用的新特性,特别是Props解构和defineModel的改进,显著提升了开发体验。响应式系统的优化也为大型应用带来了性能提升。建议项目逐步升级并采用这些新特性。

参考资料