---
URL: "/zh-CN/blog/2025-09/vue-tips.html"
LLMS_URL: "/zh-CN/blog/2025-09/vue-tips.md"
lastUpdated: true
commentabled: true
recommended: true
title: "🔥前端性能瓶颈如何突破？"
description: "Vue 3.4 Suspense+Vite 6.0模块联邦+Web Workers实现毫秒级组件加载"
date:

pageClass: "blog-page-class"
cover: "/covers/vue.svg"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: 🔥前端性能瓶颈如何突破？
description: Vue 3.4 Suspense+Vite 6.0模块联邦+Web Workers实现毫秒级组件加载
date: 2025-09-25 16:00:00 
pageClass: blog-page-class
cover: /covers/vue.svg
---

## 性能调优器 - `shallowRef` & `triggerRef` ##

### 场景痛点 ###

我们都知道 `ref` 是深度响应的。但如果一个 `ref` 包裹了层级很深或非常大的对象（比如一个包含数千条数据的列表），每次不经意的改动都可能触发深层的依赖追踪和更新，造成不必要的性能开销。

### 解决方案 ###

**shallowRef**

`shallowRef` 只追踪 `.value` 属性的变更，不对其内部的属性做响应式处理。

### 代码示例 ###

```ts
import { shallowRef, triggerRef } from 'vue'

// 假设这是一个非常庞大的对象数组
const bigList = shallowRef([
  { id: 1, name: 'Item 1', data: { /* ... */ } },
  // ... 9999 more items
]);

function updateFirstName() {
  // 这样做，视图是不会更新的！
  // 因为 shallowRef 只关心 .value 本身的替换，不关心内部属性的修改
  bigList.value[0].name = 'Updated Item 1';
  console.log('Updated name, but no re-render:', bigList.value[0].name);

  // 如果想在修改内部数据后，强制触发更新，怎么办？
  // 这就是 triggerRef 的用武之地！
  triggerRef(bigList); 
  console.log('Triggered update manually!');
}
```

### 核心思想 ###

用 `shallowRef` 接管大型数据结构，获得性能提升。当确实需要更新视图时，通过 `triggerRef` 进行一次“手动通知”。这是一种“我比Vue更懂何时更新”的精细化性能控制。

## 架构防弹衣 - `readonly` Composables ##

### 场景痛点 ###

我们封装 Composable (自定义Hook) 是为了逻辑复用和封装。但如果一个 Composable 返回了响应式状态（如 `ref`），任何使用它的组件都可以直接修改这个状态（例如 `useCounter().count.value++`），这破坏了封装性，使得状态变更的来源变得不可追溯，是潜在的BUG温床。

### 解决方案 ###

在 Composable 内部，返回状态的只读版本，并暴露专门的方法来修改它。

### 代码示例 ###

我们来改造一个经典的 useCounter。

```ts:useCounter.ts
import { ref, readonly } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);

  function increment() {
    count.value++;
  }

  function decrement() {
    count.value--;
  }

  return {
    // 将 count 包裹在 readonly() 中再暴露出去
    count: readonly(count),
    increment,
    decrement,
  };
}

// MyComponent.vue
import { useCounter } from './useCounter';

const { count, increment } = useCounter();

// count.value++; // ❌ 这行代码会报错！并给出一个友好的警告
// Uncaught TypeError: Set operation on key "value" failed: target is readonly.
// 只能通过暴露的方法来修改状态，保证了数据流的单向和可预测性
increment(); 
```

### 核心思想 ###

这是一种软件设计模式的体现：“命令查询分离”。Composable 对外只暴露可读的状态和修改状态的“命令”（方法），防止外部直接“篡改”状态，让代码的维护性和健壮性大大提升。

## 类型体操 - `generic` 泛型组件 ##

### 场景痛点 ###

我们经常需要创建一些高度可复用的基础组件，比如一个下拉选择器 `<Select>`。我们希望它既能接收用户列表 `[{id: number, name: string}]`，也能接收产品列表 `[{sku: string, title: string}]`，并且在选中时，`v-model` 和 `@change` 事件都能返回正确的、带有完整类型信息的对象，而不是 `any` 或 `object`。

### 解决方案 ###

在 `<script setup>` 中使用 `generic` 属性来定义泛型。 (Vue 3.3+ 支持)

### 代码示例 ###

```vue
<!-- GenericSelect.vue -->
<script setup lang="ts" generic="T extends { id: any }">
// generic="T" 定义了一个泛型 T
// T extends { id: any } 是一个类型约束，要求传入的对象至少有个id属性
import { computed } from 'vue';

const props = defineProps<{
  options: T[],
  labelKey: keyof T, // 强制 labelKey 必须是 T 的一个键
}>();

const emit = defineEmits<{
  (e: 'update:modelValue', value: T): void
}>();

const model = defineModel<T>(); // 假设使用 defineModel

function onSelect(option: T) {
  model.value = option;
  // emit('update:modelValue', option);
}
</script>

<template>
  <ul>
    <li v-for="option in options" :key="option.id" @click="onSelect(option)">
      {{ option[labelKey] }}
    </li>
  </ul>
</template>
```

```vue
<!-- App.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import GenericSelect from './GenericSelect.vue';

type User = { id: number; name: string; email: string };

const users: User[] = [
  { id: 1, name: 'Alice', email: 'a@a.com' },
  { id: 2, name: 'Bob', email: 'b@b.com' }
];
const selectedUser = ref<User>();

// 当你把鼠标悬浮在 selectedUser.value 上时
// VSCode会告诉你它的类型是 User | undefined，而不是any！
// v-model 绑定和事件都能完美推断出类型
</script>

<template>
  <GenericSelect
    v-model="selectedUser"
    :options="users"
    label-key="name" 
  />
  <p v-if="selectedUser">Selected Email: {{ selectedUser.email }}</p>
</template>
```

### 核心思想 ###

泛型组件让你的组件从“只能处理特定形状的数据”进化为“能处理任何符合某种模式的数据”，同时保持了端到端的类型安全。这是TypeScript与Vue结合的终极魅力之一。

## TS魔法 - as const 的妙用 ##

### 场景痛点 ###

在项目中，我们经常定义一组固定的常量，比如Tabs的名称、API返回的状态码等。

```typescript
// 普通定义
const TABS = ['profile', 'settings', 'security'];
type Tab = 'profile' | 'settings' | 'security'; // 我们需要手动维护这个类型

function setActiveTab(tab: Tab) { /* ... */ }

setActiveTab('billing'); // ❌ TS会报错，很好！
```

问题是，`Tab` 类型需要我们手动维护，如果 `TABS` 数组变了，`Tab` 类型忘了改，就会出问题。

### 解决方案： `as const` ###

`as const` 会告诉 TypeScript，这个对象/数组是“只读”的，并且它的值就是它字面的类型，而不是宽泛的 `string` 或 `number`。

### 代码示例 ###

```ts
// 使用 as const
const TABS = ['profile', 'settings', 'security'] as const;
// TABS 的类型被推断为：readonly ["profile", "settings", "security"]
// 而不是 string[]

// 自动从常量生成类型！
type Tab = typeof TABS[number];
// Tab 的类型就是 'profile' | 'settings' | 'security'
// typeof TABS 获取 TABS 常量的类型
// [number] 是一个索引访问类型，表示获取该元组所有索引位上的值的联合类型

// 现在，Tab 类型与 TABS 常量完全同步，无需手动维护！
function setActiveTab(tab: Tab) { /* ... */ }

// 在组件 prop 中使用
defineProps<{
  currentTab: Tab
}>();
```

### 核心思想 ###

`as const` 实现了“代码即文档，常量即类型”。它让你的常量定义成为单一数据源（Single Source of Truth），自动派生出精确的类型，消除了手动同步类型的繁琐和风险。

## 总结 ##

- `shallowRef`：控制响应粒度，优化性能。
- `readonly`：封装 Composable 状态，增强代码健壮性。
- `generic`：打造类型安全的通用组件。
- `as const`：自动化类型推断，减少维护成本。
