
Vue3 Best Practices
- 179 installs
- 2 repo stars
- Updated April 3, 2026
- eva813/vue3-skills
Helps with ai & agent building tasks.
About
vue3-best-practices is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- vue3-best-practices
- AI & Agent Building
- AI-coding skill
Vue3 Best Practices by the numbers
- 179 all-time installs (skills.sh)
- Ranked #3,065 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/eva813/vue3-skills --skill vue3-best-practicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 179 |
|---|---|
| repo stars | ★ 2 |
| Last updated | April 3, 2026 |
| Repository | eva813/vue3-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Vue 3 Best Practices
Comprehensive performance optimization and development guide for Vue 3 applications. Contains 45 rules across 8 categories, prioritized by impact to guide automated refactoring and code generation.
When to Apply
Reference these guidelines when:
- Writing new Vue 3 components or composables
- Implementing reactive data and computed properties
- Reviewing code for performance issues
- Refactoring from Vue 2 to Vue 3
- Optimizing bundle size or load times
- Working with state management (Pinia/Vuex)
- Implementing async operations in components
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Reactivity Performance | CRITICAL | reactivity- |
| 2 | Component Optimization | CRITICAL | component- |
| 3 | Bundle Size & Loading | HIGH | bundle- |
| 4 | Composition API | MEDIUM-HIGH | composition- |
| 5 | Template Performance | MEDIUM | template- |
| 6 | State Management | MEDIUM | state- |
| 7 | Lifecycle Optimization | LOW-MEDIUM | lifecycle- |
| 8 | Advanced Patterns | LOW | advanced- |
Quick Reference
1. Reactivity Performance (CRITICAL)
reactivity-ref-vs-reactive- Use ref for primitives, reactive for objectsreactivity-shallow-ref- Use shallowRef for large immutable objectsreactivity-computed-caching- Leverage computed property cachingreactivity-watch-vs-watcheffect- Choose appropriate watcherreactivity-unref-performance- Minimize unref calls in hot pathsreactivity-readonly-immutable- Use readonly for immutable data
2. Component Optimization (CRITICAL)
component-async-components- Use defineAsyncComponent for heavy componentscomponent-functional- Use functional components for simple presentational logiccomponent-keep-alive- Cache expensive components with keep-alivecomponent-lazy-hydration- Implement lazy hydration for non-critical componentscomponent-prop-validation- Use efficient prop validationcomponent-emit-performance- Optimize event emissions
3. Bundle Size & Loading (HIGH)
bundle-tree-shaking- Structure imports for optimal tree-shakingbundle-dynamic-imports- Use dynamic imports for code splittingbundle-plugin-imports- Use unplugin-auto-import for better DXbundle-lodash-imports- Import lodash functions individuallybundle-moment-alternatives- Use day.js instead of moment.jsbundle-icons-optimization- Optimize icon imports and usage
4. Composition API (MEDIUM-HIGH)
composition-script-setup- Prefer <script setup> for better performancecomposition-composables-reuse- Extract reusable logic into composablescomposition-provide-inject- Use provide/inject for dependency injectioncomposition-expose-selectively- Expose only necessary propertiescomposition-reactive-transform- Use reactive transform where appropriatecomposition-auto-import- Configure auto-imports for better DX
5. Template Performance (MEDIUM)
template-v-once- Use v-once for static contenttemplate-v-memo- Use v-memo for expensive list renderingtemplate-key-optimization- Optimize v-for keys for performancetemplate-conditional-rendering- Choose v-if vs v-show appropriatelytemplate-slot-performance- Optimize slot usage and scoped slotstemplate-directive-optimization- Create efficient custom directives
6. State Management (MEDIUM)
state-pinia-optimization- Optimize Pinia store structurestate-store-composition- Use store composition patternsstate-persistence- Implement efficient state persistencestate-normalization- Normalize complex state structuresstate-subscription- Optimize state subscriptionsstate-devtools- Configure devtools for debugging
7. Lifecycle Optimization (LOW-MEDIUM)
lifecycle-cleanup- Properly cleanup resources in onUnmountedlifecycle-async-setup- Handle async operations in setuplifecycle-watchers-cleanup- Clean up watchers and effectslifecycle-dom-access- Access DOM safely in lifecycle hookslifecycle-ssr-considerations- Handle SSR lifecycle differences
8. Advanced Patterns (LOW)
advanced-teleport-usage- Use Teleport for portal patternsadvanced-suspense-async- Implement Suspense with async componentsadvanced-custom-renderer- Create custom renderers when neededadvanced-compiler-macros- Use compiler macros effectivelyadvanced-plugin-development- Develop efficient Vue plugins
Framework Integration
Vite Integration
- Utilize Vite's fast HMR and build optimizations
- Configure proper chunk splitting strategies
- Use Vite plugins for Vue-specific optimizations
TypeScript Integration
- Leverage Vue 3's improved TypeScript support
- Use proper type definitions for better DX
- Configure TypeScript for optimal build performance
Testing Integration
- Use Vue Test Utils with Composition API
- Implement efficient component testing strategies
- Optimize test performance and reliability
How to Use
Read individual rule files for detailed explanations and code examples:
rules/reactivity-ref-vs-reactive.md
rules/component-async-components.md
rules/composition-script-setup.mdEach rule file contains:
- Brief explanation of why it matters
- Incorrect Vue 3 code example with explanation
- Correct Vue 3 code example with explanation
- Performance impact and measurements
- Additional context and Vue 3-specific considerations
Migration from Vue 2
Special considerations for migrating from Vue 2:
- Composition API vs Options API patterns
- Reactivity system changes and optimizations
- Component definition and registration updates
- Event handling and lifecycle changes
Performance Monitoring
Tools and techniques for monitoring Vue 3 performance:
- Vue DevTools integration
- Performance profiling with browser tools
- Bundle analysis and optimization
- Runtime performance monitoring
Lazy Loading and Code Splitting
Implement proper lazy loading and code splitting to reduce initial bundle size and improve application startup performance.
Incorrect (all code loaded upfront):
// ❌ 同步導入所有路由組件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import Dashboard from '@/views/Dashboard.vue'
import UserProfile from '@/views/UserProfile.vue'
import AdminPanel from '@/views/AdminPanel.vue'
import Settings from '@/views/Settings.vue'
// ❌ 同步導入所有組件
import HeavyChart from '@/components/HeavyChart.vue'
import DataTable from '@/components/DataTable.vue'
import VideoPlayer from '@/components/VideoPlayer.vue'
// ❌ 同步導入大型函式庫
import * as echarts from 'echarts'
import * as marked from 'marked'
import * as XLSX from 'xlsx'
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About },
{ path: '/dashboard', component: Dashboard },
{ path: '/profile', component: UserProfile },
{ path: '/admin', component: AdminPanel },
{ path: '/settings', component: Settings },
]
</script>Correct (lazy loading with code splitting):
// ✅ 路由級別的代碼分割
const routes = [
{
path: '/',
name: 'Home',
component: () => import('@/views/Home.vue')
},
{
path: '/about',
name: 'About',
component: () => import('@/views/About.vue')
},
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/Dashboard.vue')
},
{
path: '/profile',
name: 'UserProfile',
component: () => import('@/views/UserProfile.vue')
},
{
path: '/admin',
name: 'AdminPanel',
// ✅ 具名 chunk,便於調試
component: () => import(/* webpackChunkName: "admin" */ '@/views/AdminPanel.vue')
},
{
path: '/settings',
name: 'Settings',
component: () => import(/* webpackChunkName: "user-settings" */ '@/views/Settings.vue')
},
]
// ✅ 嵌套路由的懶加載
const adminRoutes = {
path: '/admin',
component: () => import('@/layouts/AdminLayout.vue'),
children: [
{
path: 'users',
component: () => import(/* webpackChunkName: "admin-users" */ '@/views/admin/Users.vue')
},
{
path: 'reports',
component: () => import(/* webpackChunkName: "admin-reports" */ '@/views/admin/Reports.vue')
},
]
}
</script>Component-Level Lazy Loading:
<!-- ❌ 同步導入大型組件 -->
<template>
<div>
<heavy-chart v-if="showChart" :data="chartData" />
<data-table :items="tableData" />
<video-player v-if="showVideo" :src="videoSrc" />
</div>
</template>
<script setup>
import HeavyChart from '@/components/HeavyChart.vue'
import DataTable from '@/components/DataTable.vue'
import VideoPlayer from '@/components/VideoPlayer.vue'
const showChart = ref(false)
const showVideo = ref(false)
</script>
<!-- ✅ 組件級別的懶加載 -->
<template>
<div>
<!-- 只在需要時才載入 -->
<Suspense v-if="showChart">
<template #default>
<LazyHeavyChart :data="chartData" />
</template>
<template #fallback>
<div class="chart-loading">載入圖表中...</div>
</template>
</Suspense>
<!-- 條件懶加載 -->
<LazyDataTable v-if="tableData.length > 0" :items="tableData" />
<!-- 視窗內才載入 -->
<LazyVideoPlayer
v-if="showVideo && isInViewport"
:src="videoSrc"
/>
</div>
</template>
<script setup>
import { defineAsyncComponent, Suspense } from 'vue'
// ✅ 定義異步組件
const LazyHeavyChart = defineAsyncComponent({
loader: () => import('@/components/HeavyChart.vue'),
loadingComponent: () => import('@/components/ChartSkeleton.vue'),
errorComponent: () => import('@/components/ErrorDisplay.vue'),
delay: 200, // 延遲顯示 loading
timeout: 3000, // 超時處理
})
const LazyDataTable = defineAsyncComponent(() =>
import(/* webpackChunkName: "data-table" */ '@/components/DataTable.vue')
)
const LazyVideoPlayer = defineAsyncComponent(() =>
import(/* webpackChunkName: "video-player" */ '@/components/VideoPlayer.vue')
)
const showChart = ref(false)
const showVideo = ref(false)
const isInViewport = ref(false)
</script>Library Lazy Loading:
// ✅ 函式庫的懶加載
// composables/useChart.js
export function useChart() {
const loadEcharts = async () => {
// ✅ 動態導入圖表庫
const echarts = await import('echarts/core')
const { LineChart, BarChart } = await import('echarts/charts')
const { GridComponent, TooltipComponent } = await import('echarts/components')
echarts.use([LineChart, BarChart, GridComponent, TooltipComponent])
return echarts
}
const createChart = async (element, options) => {
const echarts = await loadEcharts()
return echarts.init(element).setOption(options)
}
return { createChart }
}
// composables/useExcel.js
export function useExcel() {
const exportToExcel = async (data, filename) => {
// ✅ 只在導出時才載入 XLSX
const XLSX = await import('xlsx')
const ws = XLSX.utils.json_to_sheet(data)
const wb = XLSX.utils.book_new()
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1')
XLSX.writeFile(wb, filename)
}
return { exportToExcel }
}
// composables/useMarkdown.js
export function useMarkdown() {
const parseMarkdown = async (content) => {
// ✅ 只在需要解析時才載入 marked
const { marked } = await import('marked')
return marked(content)
}
return { parseMarkdown }
}
</script>Intersection Observer Lazy Loading:
<!-- ✅ 視窗內懒加載組件 -->
<template>
<div>
<!-- 其他內容 -->
<div class="content-above"></div>
<!-- 懶加載觸發點 -->
<div ref="lazyTarget" class="lazy-trigger">
<LazyExpensiveComponent v-if="isVisible" />
<div v-else class="placeholder">
即將載入内容...
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted, defineAsyncComponent } from 'vue'
const LazyExpensiveComponent = defineAsyncComponent(() =>
import('@/components/ExpensiveComponent.vue')
)
const lazyTarget = ref(null)
const isVisible = ref(false)
let observer = null
onMounted(() => {
// ✅ 使用 Intersection Observer
observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
isVisible.value = true
observer.unobserve(entry.target) // 載入後停止觀察
}
})
}, {
rootMargin: '50px', // 提前 50px 載入
threshold: 0.1, // 10% 可見時觸發
})
if (lazyTarget.value) {
observer.observe(lazyTarget.value)
}
})
onUnmounted(() => {
if (observer) {
observer.disconnect()
}
})
</script>Vite Configuration for Code Splitting:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
build: {
rollupOptions: {
output: {
// ✅ 手動配置 chunk 分割
manualChunks: {
// 核心 Vue 框架
'vue-vendor': ['vue', 'vue-router', 'pinia'],
// UI 組件庫
'ui-vendor': ['element-plus', '@element-plus/icons-vue'],
// 工具函式庫
'utils-vendor': ['lodash-es', 'dayjs', '@vueuse/core'],
// 圖表庫(較大的庫)
'chart-vendor': ['echarts'],
// 第三方工具(按需加載的庫)
'tools-vendor': ['xlsx', 'marked', 'html2canvas'],
},
// ✅ 動態 chunk 命名
chunkFileNames: (chunkInfo) => {
const facadeModuleId = chunkInfo.facadeModuleId
if (facadeModuleId) {
// 路由組件
if (facadeModuleId.includes('/views/')) {
return 'views/[name]-[hash].js'
}
// 組件
if (facadeModuleId.includes('/components/')) {
return 'components/[name]-[hash].js'
}
}
return 'chunks/[name]-[hash].js'
},
},
},
// ✅ Chunk 大小警告閾值
chunkSizeWarningLimit: 1000, // 1MB
},
})Progressive Loading Strategy:
// utils/progressiveLoader.js
export class ProgressiveLoader {
constructor() {
this.loadedModules = new Map()
this.loadingPromises = new Map()
}
// ✅ 批次預載入
async preloadRoutes(routeNames) {
const promises = routeNames.map(name => this.preloadRoute(name))
await Promise.allSettled(promises)
}
// ✅ 智能預載入(基於用戶行為)
async preloadRoute(routeName) {
if (this.loadedModules.has(routeName)) {
return this.loadedModules.get(routeName)
}
if (this.loadingPromises.has(routeName)) {
return this.loadingPromises.get(routeName)
}
const loadPromise = this.loadRouteModule(routeName)
this.loadingPromises.set(routeName, loadPromise)
try {
const module = await loadPromise
this.loadedModules.set(routeName, module)
return module
} finally {
this.loadingPromises.delete(routeName)
}
}
private async loadRouteModule(routeName) {
const routeMap = {
'Dashboard': () => import('@/views/Dashboard.vue'),
'UserProfile': () => import('@/views/UserProfile.vue'),
'Settings': () => import('@/views/Settings.vue'),
}
const loader = routeMap[routeName]
if (!loader) throw new Error(`Route ${routeName} not found`)
return loader()
}
}
// ✅ 使用漸進式載入
const progressiveLoader = new ProgressiveLoader()
// 在應用啟動時預載入關鍵路由
onMounted(() => {
// 延遲預載入,避免影響初始載入
setTimeout(() => {
progressiveLoader.preloadRoutes(['Dashboard', 'UserProfile'])
}, 2000)
})Error Handling for Lazy Loading:
<!-- ✅ 完善的錯誤處理 -->
<template>
<div>
<Suspense>
<template #default>
<AsyncComponent @error="handleError" />
</template>
<template #fallback>
<ComponentSkeleton />
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('@/components/HeavyComponent.vue'),
// ✅ 載入中顯示的組件
loadingComponent: () => import('@/components/ComponentSkeleton.vue'),
// ✅ 載入失敗時顯示的組件
errorComponent: () => import('@/components/LoadErrorDisplay.vue'),
// ✅ 延遲顯示載入組件的時間
delay: 200,
// ✅ 載入超時時間
timeout: 5000,
// ✅ 自訂錯誤處理
onError(error, retry, fail, attempts) {
console.error('Component loading failed:', error)
// 重試 3 次
if (attempts < 3) {
retry()
} else {
fail()
}
}
})
const handleError = (error) => {
console.error('Component error:', error)
// 錯誤上報或其他處理
}
</script>Performance Monitoring:
// utils/performanceMonitor.js
export function monitorLazyLoading() {
// ✅ 監控 chunk 載入時間
const observer = new PerformanceObserver((list) => {
list.getEntries().forEach((entry) => {
if (entry.name.includes('.js') && entry.initiatorType === 'script') {
console.log(`Chunk loaded: ${entry.name}`)
console.log(`Loading time: ${entry.duration}ms`)
// 上報性能數據
analytics.track('chunk_loaded', {
chunkName: entry.name,
loadTime: entry.duration,
})
}
})
})
observer.observe({ entryTypes: ['navigation', 'resource'] })
}Best Practices:
1. Route-Level Splitting: Split at route level first 2. Component Conditions: Lazy load components based on conditions 3. Library Splitting: Dynamically import large libraries 4. Chunk Naming: Use meaningful chunk names for debugging 5. Error Handling: Always handle loading failures gracefully 6. Performance Monitoring: Track chunk loading performance 7. Progressive Enhancement: Implement progressive loading strategies
Performance Impact:
# Before (no code splitting)
Initial Bundle: 1.8MB
Time to Interactive: 5.2s
First Contentful Paint: 2.1s
# After (optimized lazy loading)
Initial Bundle: 420KB (-77%)
Time to Interactive: 2.8s (-46%)
First Contentful Paint: 1.2s (-43%)
Additional Chunks: Load on demandTree-Shaking Optimization
Structure imports to maximize tree-shaking effectiveness and eliminate unused code from your final bundle.
Incorrect (imports that prevent tree-shaking):
// ❌ 全量導入會包含整個函式庫
import * as _ from 'lodash' // 整個 lodash (~70KB)
import * as dayjs from 'dayjs' // 整個 dayjs (~30KB)
import Vue from 'vue' // Vue 2 風格導入
// ❌ 預設導入可能包含不必要的代碼
import utils from '@/utils' // 整個 utils 模組
import { components } from '@/components' // 整個 components 目錄
// ❌ 動態導入但沒有正確結構化
const helper = await import('@/helpers')
const result = helper.default.processData(data)
// ❌ CSS 全量導入
import 'element-plus/dist/index.css' // 完整 CSS (~200KB)
import 'bootstrap/dist/css/bootstrap.css' // 完整 Bootstrap
</script>Correct (tree-shaking friendly imports):
// ✅ 具名導入,只包含使用的函數
import { debounce, throttle, cloneDeep } from 'lodash-es' // 只有這 3 個函數
import { format, parse, isValid } from 'date-fns' // 只有需要的日期函數
import { ref, computed, onMounted } from 'vue' // 只導入使用的 Vue API
// ✅ 直接從子路徑導入
import debounce from 'lodash-es/debounce' // 最小化導入
import format from 'date-fns/format' // 單一函數導入
// ✅ 結構化的 utils 導入
import { formatCurrency } from '@/utils/format'
import { validateEmail } from '@/utils/validation'
import { storage } from '@/utils/storage'
// ✅ 按需導入組件
import { ElButton, ElInput, ElForm } from 'element-plus'
// ✅ CSS 按需導入(使用 Vite 插件)
// vite.config.js 中配置自動導入 CSSVite Configuration for Optimal Tree-Shaking:
// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [
vue(),
// ✅ 自動導入插件
AutoImport({
imports: [
'vue',
'vue-router',
'@vueuse/core',
],
dts: true, // 生成類型定義
}),
// ✅ 組件自動導入
Components({
dts: true,
resolvers: [
// Element Plus 按需導入
ElementPlusResolver({
importStyle: 'sass', // 按需導入樣式
}),
],
}),
],
build: {
rollupOptions: {
// ✅ 手動 chunk 分割
output: {
manualChunks: {
'vue-vendor': ['vue', 'vue-router'],
'ui-vendor': ['element-plus'],
'utils-vendor': ['lodash-es', 'date-fns'],
},
},
},
},
// ✅ 優化依賴處理
optimizeDeps: {
include: ['lodash-es', 'date-fns'],
exclude: ['@vueuse/core'], // 開發時排除以利於樹搖
},
})Utils Module Structure for Tree-Shaking:
// ❌ utils/index.js - 不利於 tree-shaking
export default {
format: {
currency: (value) => { /* ... */ },
date: (value) => { /* ... */ },
phone: (value) => { /* ... */ },
},
validate: {
email: (email) => { /* ... */ },
phone: (phone) => { /* ... */ },
url: (url) => { /* ... */ },
},
// 所有函數都會被打包
}
// ✅ 分離的模組 - 利於 tree-shaking
// utils/format.js
export const formatCurrency = (value) => { /* ... */ }
export const formatDate = (value) => { /* ... */ }
export const formatPhone = (value) => { /* ... */ }
// utils/validate.js
export const validateEmail = (email) => { /* ... */ }
export const validatePhone = (phone) => { /* ... */ }
export const validateUrl = (url) => { /* ... */ }
// utils/index.js - 重新導出
export { formatCurrency, formatDate, formatPhone } from './format'
export { validateEmail, validatePhone, validateUrl } from './validate'Component Library Tree-Shaking:
<!-- ❌ 全量導入組件庫 -->
<template>
<div>
<el-button>按鈕</el-button>
<el-input v-model="input" />
</div>
</template>
<script setup>
// ❌ 導入整個 Element Plus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
</script>
<!-- ✅ 按需導入組件 -->
<template>
<div>
<el-button>按鈕</el-button>
<el-input v-model="input" />
</div>
</template>
<script setup>
// ✅ 只導入需要的組件
import { ElButton, ElInput } from 'element-plus'
import { ref } from 'vue'
const input = ref('')
</script>
<style>
/* ✅ 只導入需要的 CSS */
@import 'element-plus/es/components/button/style/css';
@import 'element-plus/es/components/input/style/css';
</style>Advanced Tree-Shaking with Composables:
// ✅ composables/index.js - 結構化導出
export { useCounter } from './useCounter'
export { useLocalStorage } from './useLocalStorage'
export { useApi } from './useApi'
export { useAuth } from './useAuth'
// ✅ 使用時只導入需要的
import { useCounter, useLocalStorage } from '@/composables'
// ✅ 或者直接從檔案導入
import { useCounter } from '@/composables/useCounter'
import { useAuth } from '@/composables/useAuth'Bundle Analysis and Monitoring:
# ✅ 分析 bundle 大小
npm run build -- --analyze
# ✅ 使用 rollup-plugin-visualizer
npm install rollup-plugin-visualizer -D// vite.config.js
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
vue(),
// ✅ Bundle 分析器
visualizer({
filename: 'dist/stats.html',
open: true,
gzipSize: true,
}),
],
})Third-Party Library Optimization:
// ❌ 會打包整個函式庫的方式
import moment from 'moment'
import * as echarts from 'echarts'
// ✅ 優化的導入方式
import dayjs from 'dayjs' // 更小的日期庫
import * as echarts from 'echarts/core' // 核心
import { LineChart, BarChart } from 'echarts/charts' // 只導入需要的圖表
import { GridComponent, TooltipComponent } from 'echarts/components'
// 注冊組件
echarts.use([LineChart, BarChart, GridComponent, TooltipComponent])Performance Impact:
# Before (無 tree-shaking 優化)
Bundle Size: 2.1MB
Gzipped: 580KB
Loading Time: 4.2s
# After (tree-shaking 優化)
Bundle Size: 1.2MB (-43%)
Gzipped: 320KB (-45%)
Loading Time: 2.4s (-43%)Best Practices:
1. Named Imports: Prefer named imports over default imports 2. Direct Imports: Import from specific paths when possible 3. Module Structure: Structure your own modules for tree-shaking 4. Bundle Analysis: Regularly analyze bundle size 5. Plugin Configuration: Use Vite plugins for automatic optimization 6. Library Selection: Choose tree-shaking friendly libraries
Note: Always verify tree-shaking effectiveness by analyzing your final bundle with tools like rollup-plugin-visualizer.
Async Components for Code Splitting
Use defineAsyncComponent to lazy-load heavy components and reduce initial bundle size for better loading performance.
Incorrect (synchronous import of heavy components):
<template>
<div>
<header-component />
<!-- ❌ 大型組件同步載入,增加初始束包大小 -->
<chart-dashboard :data="chartData" />
<rich-text-editor v-model="content" />
<data-table :items="tableData" />
<modal v-if="showModal">
<complex-form @submit="handleSubmit" />
</modal>
</div>
</template>
<script setup>
// ❌ 所有組件同步導入
import HeaderComponent from '@/components/HeaderComponent.vue'
import ChartDashboard from '@/components/ChartDashboard.vue' // ~200KB
import RichTextEditor from '@/components/RichTextEditor.vue' // ~150KB
import DataTable from '@/components/DataTable.vue' // ~100KB
import ComplexForm from '@/components/ComplexForm.vue' // ~80KB
// 總計:~530KB 額外的初始束包大小
</script>Correct (async components with proper loading states):
<template>
<div>
<header-component />
<!-- ✅ 非同步組件載入 -->
<Suspense>
<template #default>
<chart-dashboard :data="chartData" />
</template>
<template #fallback>
<div class="loading-skeleton">載入圖表中...</div>
</template>
</Suspense>
<rich-text-editor v-model="content" />
<data-table :items="tableData" />
<!-- ✅ Modal 內容延遲載入 -->
<modal v-if="showModal">
<Suspense>
<template #default>
<complex-form @submit="handleSubmit" />
</template>
<template #fallback>
<div class="form-loading">準備表單中...</div>
</template>
</Suspense>
</modal>
</div>
</template>
<script setup>
import { defineAsyncComponent } from 'vue'
import HeaderComponent from '@/components/HeaderComponent.vue'
// ✅ 定義非同步組件
const ChartDashboard = defineAsyncComponent({
loader: () => import('@/components/ChartDashboard.vue'),
loadingComponent: () => <div class="chart-loading">載入圖表...</div>,
errorComponent: () => <div class="chart-error">圖表載入失敗</div>,
delay: 200,
timeout: 5000
})
const RichTextEditor = defineAsyncComponent({
loader: () => import('@/components/RichTextEditor.vue'),
loadingComponent: () => <div class="editor-loading">載入編輯器...</div>,
delay: 100
})
const DataTable = defineAsyncComponent(() => import('@/components/DataTable.vue'))
const ComplexForm = defineAsyncComponent(() => import('@/components/ComplexForm.vue'))
</script>Advanced Pattern with Conditional Loading:
<template>
<div>
<nav-tabs v-model="activeTab" :tabs="tabs" />
<!-- ✅ 根據 tab 條件載入 -->
<keep-alive>
<component :is="currentTabComponent" />
</keep-alive>
</div>
</template>
<script setup>
import { ref, computed, defineAsyncComponent } from 'vue'
const activeTab = ref('dashboard')
// ✅ 按需載入不同的 tab 組件
const tabComponents = {
dashboard: defineAsyncComponent(() => import('@/views/DashboardTab.vue')),
analytics: defineAsyncComponent(() => import('@/views/AnalyticsTab.vue')),
settings: defineAsyncComponent(() => import('@/views/SettingsTab.vue')),
reports: defineAsyncComponent({
loader: () => import('@/views/ReportsTab.vue'),
loadingComponent: () => <div>載入報表模組...</div>,
delay: 300
})
}
const currentTabComponent = computed(() => tabComponents[activeTab.value])
</script>Performance Optimization with Preloading:
<script setup>
import { defineAsyncComponent, onMounted } from 'vue'
// ✅ 基本非同步載入
const HeavyComponent = defineAsyncComponent(() => import('@/components/HeavyComponent.vue'))
// ✅ 預載入策略
onMounted(() => {
// 延遲預載入可能需要的組件
setTimeout(() => {
import('@/components/UpcomingFeature.vue')
import('@/components/SearchModal.vue')
}, 2000)
})
// ✅ 滑鼠懸停時預載入
const preloadComponent = () => {
import('@/components/TooltipContent.vue')
}
</script>
<template>
<div>
<heavy-component />
<button @mouseenter="preloadComponent">顯示提示</button>
</div>
</template>With Error Handling and Retry:
<script setup>
import { defineAsyncComponent, ref } from 'vue'
const retryCount = ref(0)
const maxRetries = 3
const ResilientComponent = defineAsyncComponent({
loader: async () => {
try {
return await import('@/components/ImportantFeature.vue')
} catch (error) {
if (retryCount.value < maxRetries) {
retryCount.value++
console.log(`重試載入組件,第 ${retryCount.value} 次`)
// 延遲重試
await new Promise(resolve => setTimeout(resolve, 1000 * retryCount.value))
return import('@/components/ImportantFeature.vue')
}
throw error
}
},
loadingComponent: () => <div>載入重要功能中...</div>,
errorComponent: () => (
<div class="error-state">
<p>載入失敗 ({retryCount.value}/{maxRetries})</p>
<button onClick={() => location.reload()}>重新載入頁面</button>
</div>
),
delay: 200,
timeout: 10000
})
</script>Bundle Analysis Impact:
# Before (同步載入)
Initial Bundle: 1.2MB
Time to Interactive: 3.2s
# After (非同步載入)
Initial Bundle: 650KB (-46%)
Time to Interactive: 1.8s (-44%)
Lazy Chunks: dashboard.js (200KB), editor.js (150KB)Best Practices:
1. Critical Path: Keep critical components synchronous 2. Loading States: Always provide loading components 3. Error Handling: Implement fallback error components 4. Preloading: Preload on user interaction hints 5. Cache Strategy: Use keep-alive for expensive components 6. Bundle Analysis: Monitor chunk sizes regularly
Note: Use async components for any component larger than 50KB or components not immediately visible to users.
Script Setup Performance
Prefer <script setup> over regular <script> for better performance, smaller bundle size, and improved developer experience.
Incorrect (regular script with setup function):
<template>
<div>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<child-component :data="computedData" @update="handleUpdate" />
</div>
</template>
<script>
import { ref, computed, defineComponent } from 'vue'
import ChildComponent from './ChildComponent.vue'
export default defineComponent({
components: {
ChildComponent
},
setup() {
const title = ref('My App')
const count = ref(0)
const computedData = computed(() => {
return { count: count.value * 2 }
})
const increment = () => {
count.value++
}
const handleUpdate = (newValue) => {
count.value = newValue
}
return {
title,
count,
computedData,
increment,
handleUpdate
}
}
})
</script>Correct (script setup):
<template>
<div>
<h1>{{ title }}</h1>
<p>Count: {{ count }}</p>
<button @click="increment">+</button>
<ChildComponent :data="computedData" @update="handleUpdate" />
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import ChildComponent from './ChildComponent.vue'
// ✅ 直接定義,無需 return
const title = ref('My App')
const count = ref(0)
// ✅ 自動推斷類型
const computedData = computed(() => ({ count: count.value * 2 }))
// ✅ 函數自動暴露
const increment = () => {
count.value++
}
const handleUpdate = (newValue) => {
count.value = newValue
}
</script>Advanced Script Setup with TypeScript:
<template>
<div>
<user-card :user="user" @edit="editUser" />
<form @submit.prevent="saveUser">
<input v-model="form.name" placeholder="Name" />
<input v-model="form.email" type="email" placeholder="Email" />
<button type="submit" :disabled="!isFormValid">Save</button>
</form>
</div>
</template>
<script setup lang="ts">
import { ref, computed, reactive } from 'vue'
import type { User } from '@/types'
import UserCard from '@/components/UserCard.vue'
// ✅ Interface 定義
interface UserForm {
name: string
email: string
}
// ✅ Props 定義(自動推斷類型)
const props = defineProps<{
userId: number
initialData?: User
}>()
// ✅ Emits 定義
const emit = defineEmits<{
save: [user: User]
cancel: []
}>()
// ✅ 響應式資料
const user = ref<User | null>(null)
const form = reactive<UserForm>({
name: '',
email: ''
})
// ✅ Computed 屬性
const isFormValid = computed(() =>
form.name.length > 0 && /\S+@\S+\.\S+/.test(form.email)
)
// ✅ 方法定義
const editUser = (userData: User) => {
user.value = userData
Object.assign(form, {
name: userData.name,
email: userData.email
})
}
const saveUser = () => {
if (!isFormValid.value) return
const updatedUser: User = {
...user.value!,
name: form.name,
email: form.email
}
emit('save', updatedUser)
}
// ✅ 生命周期 hooks
import { onMounted } from 'vue'
onMounted(async () => {
if (props.initialData) {
editUser(props.initialData)
}
})
</script>Performance Benefits:
1. Smaller Bundle: Less boilerplate code 2. Faster Compilation: Compile-time optimizations 3. Better Tree Shaking: Unused imports are removed 4. Improved DX: Auto-imports and better IDE support 5. Type Safety: Better TypeScript integration
Migration Tips:
<!-- Before (Options API) -->
<script>
export default {
data() {
return { count: 0 }
},
computed: {
doubled() { return this.count * 2 }
},
methods: {
increment() { this.count++ }
}
}
</script>
<!-- After (Script Setup) -->
<script setup>
const count = ref(0)
const doubled = computed(() => count.value * 2)
const increment = () => count.value++
</script>Note: <script setup> provides compile-time optimizations and should be the default choice for new Vue 3 projects.
Computed Property Caching Optimization
Leverage Vue's computed property caching to avoid expensive recalculations and optimize rendering performance.
Incorrect (method calls in template, recalculates on every render):
<template>
<div>
<!-- ❌ 每次渲染都重新計算 -->
<div class="total">總計: {{ calculateTotal() }}</div>
<div class="tax">稅額: {{ calculateTax() }}</div>
<div class="shipping">運費: {{ calculateShipping() }}</div>
<!-- ❌ 複雜過濾在每次渲染時執行 -->
<ul>
<li v-for="item in filterExpensiveItems()" :key="item.id">
{{ item.name }}: ${{ formatPrice(item.price) }}
</li>
</ul>
<!-- ❌ 排序在每次鍵盤輸入時重新執行 -->
<input v-model="searchQuery" placeholder="搜索..." />
<div v-for="result in sortSearchResults()" :key="result.id">
{{ result.title }}
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([/* 大量商品資料 */])
const searchQuery = ref('')
// ❌ 方法每次都重新計算
const calculateTotal = () => {
console.log('計算總計...') // 每次渲染都會執行
return items.value.reduce((sum, item) => sum + item.price, 0)
}
const calculateTax = () => {
console.log('計算稅額...') // 每次渲染都會執行
return calculateTotal() * 0.08
}
const filterExpensiveItems = () => {
console.log('過濾昂貴商品...') // 每次渲染都會執行
return items.value.filter(item => item.price > 100)
}
const sortSearchResults = () => {
console.log('排序搜尻結果...') // 每次輸入都會執行
return items.value
.filter(item => item.name.includes(searchQuery.value))
.sort((a, b) => a.name.localeCompare(b.name))
}
</script>Correct (computed properties with automatic caching):
<template>
<div>
<!-- ✅ 快取計算結果,只在依賴改變時重算 -->
<div class="total">總計: {{ totalAmount }}</div>
<div class="tax">稅額: {{ taxAmount }}</div>
<div class="shipping">運費: {{ shippingCost }}</div>
<!-- ✅ 快取的過濾結果 -->
<ul>
<li v-for="item in expensiveItems" :key="item.id">
{{ item.name }}: ${{ formatPrice(item.price) }}
</li>
</ul>
<!-- ✅ 快取的搜索和排序結果 -->
<input v-model="searchQuery" placeholder="搜索..." />
<div v-for="result in sortedSearchResults" :key="result.id">
{{ result.title }}
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const items = ref([/* 大量商品資料 */])
const searchQuery = ref('')
// ✅ Computed 屬性自動快取
const totalAmount = computed(() => {
console.log('計算總計...') // 只在 items 改變時執行
return items.value.reduce((sum, item) => sum + item.price, 0)
})
const taxAmount = computed(() => {
console.log('計算稅額...') // 只在 totalAmount 改變時執行
return totalAmount.value * 0.08
})
const shippingCost = computed(() => {
// 基於總額的運費計算
const total = totalAmount.value
if (total > 1000) return 0
if (total > 500) return 50
return 100
})
const expensiveItems = computed(() => {
console.log('過濾昂貴商品...') // 只在 items 改變時執行
return items.value.filter(item => item.price > 100)
})
const filteredItems = computed(() => {
if (!searchQuery.value) return items.value
console.log('過濾搜索結果...') // 只在 items 或 searchQuery 改變時執行
return items.value.filter(item =>
item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
)
})
const sortedSearchResults = computed(() => {
console.log('排序搜索結果...') // 只在 filteredItems 改變時執行
return filteredItems.value
.slice() // 避免修改原陣列
.sort((a, b) => a.name.localeCompare(b.name))
})
</script>Advanced Computed Patterns:
<script setup>
import { ref, computed, watchEffect } from 'vue'
const products = ref([])
const category = ref('all')
const sortBy = ref('name')
const sortOrder = ref('asc')
// ✅ 多層 computed 依賴鏈
const filteredProducts = computed(() => {
if (category.value === 'all') return products.value
return products.value.filter(p => p.category === category.value)
})
const sortedProducts = computed(() => {
return filteredProducts.value
.slice()
.sort((a, b) => {
const factor = sortOrder.value === 'asc' ? 1 : -1
return a[sortBy.value].localeCompare(b[sortBy.value]) * factor
})
})
// ✅ 複雜的統計計算
const productStatistics = computed(() => {
const products = filteredProducts.value
return {
total: products.length,
averagePrice: products.reduce((sum, p) => sum + p.price, 0) / products.length || 0,
priceRange: {
min: Math.min(...products.map(p => p.price)) || 0,
max: Math.max(...products.map(p => p.price)) || 0
},
categoryCounts: products.reduce((acc, p) => {
acc[p.category] = (acc[p.category] || 0) + 1
return acc
}, {})
}
})
// ✅ Getter 和 Setter 的 computed
const selectedProductIds = ref(new Set())
const selectedProducts = computed({
get: () => {
return products.value.filter(p => selectedProductIds.value.has(p.id))
},
set: (newProducts) => {
selectedProductIds.value = new Set(newProducts.map(p => p.id))
}
})
// ✅ 條件性 computed(避免不必要的計算)
const expensiveAnalytics = computed(() => {
// 只在有足夠資料時才進行複雜計算
if (products.value.length < 100) return null
console.log('執行昂貴的分析計算...')
return performExpensiveAnalytics(products.value)
})
</script>Performance Monitoring:
<script setup>
import { ref, computed, watchEffect } from 'vue'
const items = ref([])
// ✅ 使用 watchEffect 監控計算性能
const expensiveComputed = computed(() => {
const start = performance.now()
const result = items.value
.filter(item => item.active)
.map(item => ({ ...item, processed: true }))
.sort((a, b) => b.priority - a.priority)
const end = performance.now()
console.log(`計算耗時: ${end - start}ms`)
return result
})
// ✅ 監控 computed 的重新計算頻率
let computeCount = 0
watchEffect(() => {
expensiveComputed.value // 觸發計算
console.log(`Computed 已重新計算 ${++computeCount} 次`)
})
</script>Performance Benefits:
1. Automatic Caching: Results cached until dependencies change 2. Dependency Tracking: Only recalculates when relevant data changes 3. Memory Efficiency: Old cached values are garbage collected 4. Performance Monitoring: Easy to profile expensive calculations 5. Composability: Computed properties can depend on other computed properties
Best Practices:
<script setup>
// ✅ 保持 computed 純函數
const pureComputed = computed(() => {
return items.value.map(item => item.name.toUpperCase())
})
// ❌ 避免副作用
const impureComputed = computed(() => {
localStorage.setItem('lastComputed', Date.now()) // 不要這樣做
return items.value.length
})
// ✅ 分解複雜計算為多個 computed
const step1 = computed(() => items.value.filter(item => item.active))
const step2 = computed(() => step1.value.map(item => transformItem(item)))
const finalResult = computed(() => step2.value.sort(compareItems))
</script>Note: Computed properties should be pure functions without side effects for optimal caching behavior.
Ref vs Reactive Selection
Use ref() for primitives and reactive() for objects to optimize reactivity performance and avoid unnecessary overhead.
Incorrect (reactive for primitives, ref for objects):
<script setup>
import { reactive, ref } from 'vue'
// 不佳:對基本型別使用 reactive
const count = reactive({ value: 0 })
const message = reactive({ text: 'hello' })
// 不佳:對複雜物件使用 ref
const user = ref({
id: 1,
name: 'John',
preferences: {
theme: 'dark',
language: 'en'
}
})
function updateUser() {
// 需要 .value 且不直觀
user.value.name = 'Jane'
}
</script>Correct (ref for primitives, reactive for objects):
<script setup>
import { reactive, ref } from 'vue'
// ✅ 對基本型別使用 ref
const count = ref(0)
const message = ref('hello')
// ✅ 對複雜物件使用 reactive
const user = reactive({
id: 1,
name: 'John',
preferences: {
theme: 'dark',
language: 'en'
}
})
function updateUser() {
// 直接訪問,更直觀
user.name = 'Jane'
}
function increment() {
// 對基本型別使用 .value
count.value++
}
</script>Performance Benefits:
1. Memory Efficiency: ref has less overhead for primitives 2. Reactivity Tracking: More efficient proxy creation 3. Developer Experience: More intuitive API usage 4. TypeScript Support: Better type inference
Additional Guidelines:
<script setup>
// ✅ 混合使用 - 根據資料類型選擇
const loading = ref(false) // 基本型別用 ref
const error = ref(null) // 可能是 null 的值用 ref
const items = reactive([]) // 陣列用 reactive
const form = reactive({ // 物件用 reactive
email: '',
password: ''
})
// ✅ 解構 reactive 時使用 toRefs
const { email, password } = toRefs(form)
</script>Note: When destructuring reactive objects, always use toRefs() to maintain reactivity.
Template Performance Optimization
Optimize template structures and directives for efficient rendering and minimal DOM operations.
Incorrect (inefficient template patterns):
<!-- ❌ 無效的條件渲染 -->
<template>
<div>
<!-- 每次都會創建/銷毀 DOM -->
<expensive-component v-if="isVisible" />
<expensive-component v-if="!isVisible" style="display: none" />
<!-- 在循環中使用複雜計算 -->
<div v-for="item in items" :key="item.id">
<span>{{ formatPrice(item.price, item.currency, item.tax) }}</span>
<span>{{ new Date(item.created).toLocaleDateString() }}</span>
<!-- 每次重渲染都會執行複雜計算 -->
</div>
<!-- 不必要的包裝元素 -->
<div>
<div>
<div>
<span>{{ message }}</span>
</div>
</div>
</div>
<!-- 低效的事件處理 -->
<button
v-for="item in items"
:key="item.id"
@click="() => handleClick(item.id)"
>
{{ item.name }}
</button>
<!-- 不必要的響應性 -->
<div v-for="item in items" :key="item.id">
{{ item.name.toUpperCase() }}
</div>
</div>
</template>
<script setup>
const items = ref([])
const isVisible = ref(true)
// ❌ 在 template 中直接調用函數
const formatPrice = (price, currency, tax) => {
// 複雜的格式化邏輯
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(price * (1 + tax))
}
const handleClick = (id) => {
console.log('Clicked:', id)
}
</script>Correct (optimized template patterns):
<!-- ✅ 高效的模板結構 -->
<template>
<div>
<!-- 使用 v-show 避免重複創建/銷毀 -->
<expensive-component v-show="isVisible" />
<!-- 預計算複雜的數據 -->
<div v-for="item in formattedItems" :key="item.id">
<span>{{ item.formattedPrice }}</span>
<span>{{ item.formattedDate }}</span>
</div>
<!-- 避免不必要的包裝 -->
<span>{{ message }}</span>
<!-- 提升事件處理器 -->
<button
v-for="item in items"
:key="item.id"
@click="handleClick"
:data-id="item.id"
>
{{ item.name }}
</button>
<!-- 使用計算屬性預處理數據 -->
<div v-for="item in uppercaseItems" :key="item.id">
{{ item.displayName }}
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
const items = ref([])
const isVisible = ref(true)
// ✅ 使用計算屬性預處理複雜數據
const formattedItems = computed(() => {
return items.value.map(item => ({
...item,
formattedPrice: formatPrice(item.price, item.currency, item.tax),
formattedDate: new Date(item.created).toLocaleDateString(),
}))
})
const uppercaseItems = computed(() => {
return items.value.map(item => ({
...item,
displayName: item.name.toUpperCase(),
}))
})
// ✅ 提升事件處理器,避免內聯函數
const handleClick = (event) => {
const id = event.target.dataset.id
console.log('Clicked:', id)
}
// ✅ 將格式化邏輯移到計算屬性中
const formatPrice = (price, currency, tax) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: currency
}).format(price * (1 + tax))
}
</script>Advanced Template Optimization Patterns:
<!-- ✅ 條件渲染最佳化 -->
<template>
<div>
<!-- 使用 template 標籤避免不必要的包裝 -->
<template v-if="showUserInfo">
<h2>用戶信息</h2>
<p>{{ user.name }}</p>
<p>{{ user.email }}</p>
</template>
<!-- 使用 v-show 對於頻繁切換的元素 -->
<div v-show="isModalVisible" class="modal">
<!-- 模態框內容 -->
</div>
<!-- 條件組合優化 -->
<div v-if="user && user.isActive && user.hasPermission">
<!-- 合併多個條件 -->
</div>
<!-- 使用計算屬性簡化複雜條件 -->
<div v-if="shouldShowAdvancedFeatures">
<!-- 複雜邏輯移到計算屬性 -->
</div>
</div>
</template>
<script setup>
const user = ref(null)
const isModalVisible = ref(false)
const userRole = ref('user')
const featureFlags = ref({})
// ✅ 複雜條件邏輯使用計算屬性
const shouldShowAdvancedFeatures = computed(() => {
return user.value?.isActive &&
userRole.value === 'admin' &&
featureFlags.value.advancedFeatures
})
const showUserInfo = computed(() => {
return user.value && user.value.name && user.value.email
})
</script>List Rendering Optimization:
<!-- ✅ 高效的列表渲染 -->
<template>
<div>
<!-- 使用穩定的 key -->
<div
v-for="item in optimizedItems"
:key="item.id"
class="item"
>
<!-- 避免在循環中使用複雜表達式 -->
<h3>{{ item.title }}</h3>
<p>{{ item.description }}</p>
<span class="price">{{ item.formattedPrice }}</span>
<!-- 條件渲染優化 -->
<badge v-if="item.isNew" type="new" />
<badge v-else-if="item.isPopular" type="popular" />
</div>
<!-- 虛擬滾動大數據列表 -->
<virtual-list
v-if="items.length > 1000"
:items="items"
:item-height="60"
:container-height="400"
>
<template #default="{ item }">
<list-item :data="item" />
</template>
</virtual-list>
</div>
</template>
<script setup>
import VirtualList from '@/components/VirtualList.vue'
import ListItem from '@/components/ListItem.vue'
const items = ref([])
// ✅ 預處理列表數據
const optimizedItems = computed(() => {
return items.value.map(item => ({
...item,
formattedPrice: formatCurrency(item.price),
isNew: Date.now() - item.createdAt < 7 * 24 * 60 * 60 * 1000,
isPopular: item.views > 1000,
}))
})
const formatCurrency = (price) => {
return new Intl.NumberFormat('zh-TW', {
style: 'currency',
currency: 'TWD'
}).format(price)
}
</script>Event Handling Optimization:
<!-- ✅ 事件處理最佳化 -->
<template>
<div>
<!-- 事件委託 -->
<div @click="handleListClick" class="item-list">
<div
v-for="item in items"
:key="item.id"
:data-action="item.action"
:data-id="item.id"
class="item"
>
{{ item.name }}
</div>
</div>
<!-- 防抖處理 -->
<input
v-model="searchTerm"
@input="debouncedSearch"
placeholder="搜尋..."
/>
<!-- 節流處理 -->
<div
@scroll="throttledScroll"
class="scrollable-content"
>
<!-- 滾動內容 -->
</div>
<!-- 鍵盤事件優化 -->
<input
@keydown.enter="handleSubmit"
@keydown.esc="handleCancel"
@keydown.ctrl.s.prevent="handleSave"
/>
</div>
</template>
<script setup>
import { debounce, throttle } from 'lodash-es'
const items = ref([])
const searchTerm = ref('')
// ✅ 事件委託處理
const handleListClick = (event) => {
const target = event.target.closest('[data-action]')
if (!target) return
const action = target.dataset.action
const id = target.dataset.id
switch (action) {
case 'edit':
editItem(id)
break
case 'delete':
deleteItem(id)
break
default:
viewItem(id)
}
}
// ✅ 防抖搜尋
const debouncedSearch = debounce((event) => {
performSearch(event.target.value)
}, 300)
// ✅ 節流滾動
const throttledScroll = throttle((event) => {
handleScroll(event)
}, 100)
const performSearch = (term) => {
// 執行搜尋邏輯
}
const handleScroll = (event) => {
// 處理滾動邏輯
}
</script>Component Communication Optimization:
<!-- ✅ 高效的組件通信 -->
<template>
<div>
<!-- Provide/Inject 避免 prop drilling -->
<user-context-provider :user="currentUser">
<user-dashboard />
</user-context-provider>
<!-- 事件匯流 -->
<div @click="handleBubbledEvents">
<action-button action="save" />
<action-button action="cancel" />
<action-button action="delete" />
</div>
<!-- 組件懶載入 -->
<Suspense>
<template #default>
<async-heavy-component v-if="showHeavyComponent" />
</template>
<template #fallback>
<component-skeleton />
</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent, provide } from 'vue'
const AsyncHeavyComponent = defineAsyncComponent(() =>
import('@/components/HeavyComponent.vue')
)
const currentUser = ref(null)
// ✅ Provide context to avoid prop drilling
provide('user', currentUser)
// ✅ 事件匯流處理
const handleBubbledEvents = (event) => {
const button = event.target.closest('[data-action]')
if (!button) return
const action = button.dataset.action
emit('action', { type: action, timestamp: Date.now() })
}
</script>Memory Management in Templates:
<!-- ✅ 記憶體管理優化 -->
<template>
<div>
<!-- 避免記憶體洩漏 -->
<div v-if="!isDestroyed">
<!-- 組件內容 -->
</div>
<!-- 大型列表使用虛擬滾動 -->
<recycle-scroller
v-if="largeItems.length > 100"
class="scroller"
:items="largeItems"
:item-size="80"
key-field="id"
v-slot="{ item }"
>
<item-component :data="item" />
</recycle-scroller>
<!-- 圖片懶載入 -->
<img
v-for="image in images"
:key="image.id"
:data-src="image.url"
class="lazy-image"
loading="lazy"
/>
</div>
</template>
<script setup>
import { RecycleScroller } from 'vue-virtual-scroller'
const largeItems = ref([])
const images = ref([])
const isDestroyed = ref(false)
onBeforeUnmount(() => {
isDestroyed.value = true
// 清理資源
largeItems.value = []
images.value = []
})
</script>Performance Monitoring for Templates:
// composables/useTemplatePerformance.js
export function useTemplatePerformance() {
const renderTimes = ref([])
const measureRenderTime = (componentName) => {
const startTime = performance.now()
nextTick(() => {
const endTime = performance.now()
const renderTime = endTime - startTime
renderTimes.value.push({
component: componentName,
renderTime,
timestamp: Date.now(),
})
// 記錄長時間渲染
if (renderTime > 16) { // 超過一個動畫幀
console.warn(`Slow render detected: ${componentName} took ${renderTime}ms`)
}
})
}
const getAverageRenderTime = (componentName) => {
const componentRenders = renderTimes.value.filter(r => r.component === componentName)
if (componentRenders.length === 0) return 0
const totalTime = componentRenders.reduce((sum, r) => sum + r.renderTime, 0)
return totalTime / componentRenders.length
}
return {
measureRenderTime,
getAverageRenderTime,
renderTimes: readonly(renderTimes),
}
}Best Practices:
1. Computed Properties: Use computed properties for complex calculations 2. Event Delegation: Use event delegation for list items 3. v-show vs v-if: Choose based on toggle frequency 4. Stable Keys: Use stable, unique keys for v-for 5. Template Fragments: Use <template> to avoid wrapper elements 6. Virtual Scrolling: Use for large lists (>100 items) 7. Lazy Loading: Load images and components on demand 8. Memory Management: Clean up resources in onBeforeUnmount
Performance Impact:
# Template optimization results:
Render Time: -40% (16ms → 9.6ms)
DOM Operations: -60% (100 → 40 ops/render)
Memory Usage: -35% (45MB → 29MB)
First Contentful Paint: -25% improvement