vue-composition-api
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseVue Composition API
Vue Composition API
Master the Vue 3 Composition API for building scalable, maintainable
Vue applications with better code organization and reusability.
掌握Vue 3 Composition API,构建具有更优代码组织性和可复用性的可扩展、可维护Vue应用程序。
Setup Function Fundamentals
Setup函数基础
The function is the entry point for using the Composition API:
setup()typescript
import { ref, computed, onMounted } from 'vue';
export default {
props: ['initialCount'],
setup(props, context) {
// props is reactive
console.log(props.initialCount);
// context provides attrs, slots, emit, expose
const { attrs, slots, emit, expose } = context;
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
emit('update', count.value);
}
onMounted(() => {
console.log('Component mounted');
});
// Expose public methods
expose({ increment });
// Return values to template
return {
count,
doubled,
increment
};
}
};setup()typescript
import { ref, computed, onMounted } from 'vue';
export default {
props: ['initialCount'],
setup(props, context) {
// props是响应式的
console.log(props.initialCount);
// context提供attrs、slots、emit、expose
const { attrs, slots, emit, expose } = context;
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
emit('update', count.value);
}
onMounted(() => {
console.log('组件已挂载');
});
// 暴露公共方法
expose({ increment });
// 返回值给模板使用
return {
count,
doubled,
increment
};
}
};Script Setup Syntax
Script Setup语法
Modern Vue 3 uses for cleaner syntax:
<script setup>typescript
<script setup lang="ts">
import { ref, computed } from 'vue';
// Top-level bindings automatically exposed to template
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
// Props and emits use compiler macros
interface Props {
initialCount?: number;
}
const props = withDefaults(defineProps<Props>(), {
initialCount: 0
});
const emit = defineEmits<{
update: [value: number];
}>();
</script>
<template>
<div>
<p>Count: {{ count }}</p>
<p>Doubled: {{ doubled }}</p>
<button @click="increment">Increment</button>
</div>
</template>现代Vue 3使用实现更简洁的语法:
<script setup>typescript
<script setup lang="ts">
import { ref, computed } from 'vue';
// 顶层绑定会自动暴露给模板
const count = ref(0);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
// 使用编译器宏定义Props和Emits
interface Props {
initialCount?: number;
}
const props = withDefaults(defineProps<Props>(), {
initialCount: 0
});
const emit = defineEmits<{
update: [value: number];
}>();
</script>
<template>
<div>
<p>计数:{{ count }}</p>
<p>双倍值:{{ doubled }}</p>
<button @click="increment">增加</button>
</div>
</template>Ref vs Reactive - When to Use Each
Ref与Reactive - 各自适用场景
Use Ref For
使用Ref的场景
typescript
import { ref } from 'vue';
// Primitives
const count = ref(0);
const name = ref('John');
const isActive = ref(true);
// Single object that needs replacement
const user = ref({ name: 'John', age: 30 });
user.value = { name: 'Jane', age: 25 }; // Works
// Arrays that need replacement
const items = ref([1, 2, 3]);
items.value = [4, 5, 6]; // Workstypescript
import { ref } from 'vue';
// 基本类型
const count = ref(0);
const name = ref('John');
const isActive = ref(true);
// 需要被整体替换的单个对象
const user = ref({ name: 'John', age: 30 });
user.value = { name: 'Jane', age: 25 }; // 有效
// 需要被整体替换的数组
const items = ref([1, 2, 3]);
items.value = [4, 5, 6]; // 有效Use Reactive For
使用Reactive的场景
typescript
import { reactive, toRefs } from 'vue';
// Complex nested objects
const state = reactive({
user: { name: 'John', age: 30 },
settings: { theme: 'dark', notifications: true },
posts: []
});
// Group related state
const formState = reactive({
name: '',
email: '',
password: '',
errors: {}
});
// Convert to refs for destructuring
const { name, email } = toRefs(formState);typescript
import { reactive, toRefs } from 'vue';
// 复杂嵌套对象
const state = reactive({
user: { name: 'John', age: 30 },
settings: { theme: 'dark', notifications: true },
posts: []
});
// 相关状态分组
const formState = reactive({
name: '',
email: '',
password: '',
errors: {}
});
// 转换为ref以支持解构
const { name, email } = toRefs(formState);Avoid Reactive For
避免使用Reactive的场景
typescript
// DON'T: Replacing entire reactive object loses reactivity
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // Breaks reactivity!
// DO: Use ref instead
const state = ref({ count: 0 });
state.value = { count: 1 }; // Workstypescript
// 错误示例:替换整个reactive对象会丢失响应性
let state = reactive({ count: 0 });
state = reactive({ count: 1 }); // 破坏响应性!
// 正确做法:改用ref
const state = ref({ count: 0 });
state.value = { count: 1 }; // 有效Computed Properties Patterns
计算属性模式
Basic Computed
基础计算属性
typescript
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});typescript
import { ref, computed } from 'vue';
const firstName = ref('John');
const lastName = ref('Doe');
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});Writable Computed
可写计算属性
typescript
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(value) {
const names = value.split(' ');
firstName.value = names[0] || '';
lastName.value = names[1] || '';
}
});
// Can now set
fullName.value = 'Jane Smith';typescript
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(value) {
const names = value.split(' ');
firstName.value = names[0] || '';
lastName.value = names[1] || '';
}
});
// 现在可以直接设置
fullName.value = 'Jane Smith';Computed with Complex Logic
包含复杂逻辑的计算属性
typescript
interface Product {
id: number;
name: string;
price: number;
quantity: number;
}
const cart = ref<Product[]>([]);
const cartSummary = computed(() => {
const total = cart.value.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
const itemCount = cart.value.reduce((sum, item) =>
sum + item.quantity, 0
);
const tax = total * 0.08;
const grandTotal = total + tax;
return {
total,
itemCount,
tax,
grandTotal
};
});typescript
interface Product {
id: number;
name: string;
price: number;
quantity: number;
}
const cart = ref<Product[]>([]);
const cartSummary = computed(() => {
const total = cart.value.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
const itemCount = cart.value.reduce((sum, item) =>
sum + item.quantity, 0
);
const tax = total * 0.08;
const grandTotal = total + tax;
return {
total,
itemCount,
tax,
grandTotal
};
});Watch and WatchEffect
Watch与WatchEffect
Watch - Explicit Dependencies
Watch - 显式依赖
typescript
import { ref, watch } from 'vue';
const count = ref(0);
const name = ref('');
// Watch single source
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
// Watch multiple sources
watch(
[count, name],
([newCount, newName], [oldCount, oldName]) => {
console.log('Multiple values changed');
}
);
// Watch reactive object property
const user = reactive({ name: 'John', age: 30 });
watch(
() => user.name,
(newName) => {
console.log(`Name changed to ${newName}`);
}
);
// Deep watch
watch(
user,
(newUser) => {
console.log('User changed:', newUser);
},
{ deep: true }
);typescript
import { ref, watch } from 'vue';
const count = ref(0);
const name = ref('');
// 监听单个源
watch(count, (newValue, oldValue) => {
console.log(`计数从${oldValue}变为${newValue}`);
});
// 监听多个源
watch(
[count, name],
([newCount, newName], [oldCount, oldName]) => {
console.log('多个值已变更');
}
);
// 监听响应式对象的属性
const user = reactive({ name: 'John', age: 30 });
watch(
() => user.name,
(newName) => {
console.log(`姓名变更为${newName}`);
}
);
// 深度监听
watch(
user,
(newUser) => {
console.log('用户信息变更:', newUser);
},
{ deep: true }
);WatchEffect - Auto Tracking
WatchEffect - 自动追踪依赖
typescript
import { ref, watchEffect } from 'vue';
const count = ref(0);
const multiplier = ref(2);
// Automatically tracks dependencies
watchEffect(() => {
console.log(`Result: ${count.value * multiplier.value}`);
});
// Runs immediately and whenever dependencies changetypescript
import { ref, watchEffect } from 'vue';
const count = ref(0);
const multiplier = ref(2);
// 自动追踪依赖
watchEffect(() => {
console.log(`结果: ${count.value * multiplier.value}`);
});
// 立即执行,并在依赖变更时重新执行Advanced Watch Options
高级Watch选项
typescript
const data = ref(null);
watch(
source,
(newValue, oldValue) => {
// Callback logic
},
{
immediate: true, // Run immediately
deep: true, // Deep watch objects
flush: 'post', // Timing: 'pre' | 'post' | 'sync'
onTrack(e) { // Debug
console.log('tracked', e);
},
onTrigger(e) { // Debug
console.log('triggered', e);
}
}
);
// Stop watching
const stop = watch(source, callback);
stop(); // Cleanuptypescript
const data = ref(null);
watch(
source,
(newValue, oldValue) => {
// 回调逻辑
},
{
immediate: true, // 立即执行
deep: true, // 深度监听对象
flush: 'post', // 执行时机: 'pre' | 'post' | 'sync'
onTrack(e) { // 调试用
console.log('已追踪', e);
},
onTrigger(e) { // 调试用
console.log('已触发', e);
}
}
);
// 停止监听
const stop = watch(source, callback);
stop(); // 清理Lifecycle Hooks in Composition API
Composition API中的生命周期钩子
typescript
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated
} from 'vue';
export default {
setup() {
onBeforeMount(() => {
console.log('Before mount');
});
onMounted(() => {
console.log('Mounted');
// DOM is available
// Setup event listeners, fetch data
});
onBeforeUpdate(() => {
console.log('Before update');
});
onUpdated(() => {
console.log('Updated');
// DOM has been updated
});
onBeforeUnmount(() => {
console.log('Before unmount');
// Cleanup before unmount
});
onUnmounted(() => {
console.log('Unmounted');
// Final cleanup
});
onErrorCaptured((err, instance, info) => {
console.error('Error captured:', err, info);
return false; // Stop propagation
});
// For components wrapped in <KeepAlive>
onActivated(() => {
console.log('Component activated');
});
onDeactivated(() => {
console.log('Component deactivated');
});
}
};typescript
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured,
onActivated,
onDeactivated
} from 'vue';
export default {
setup() {
onBeforeMount(() => {
console.log('挂载前');
});
onMounted(() => {
console.log('已挂载');
// DOM已可用
// 设置事件监听器、获取数据
});
onBeforeUpdate(() => {
console.log('更新前');
});
onUpdated(() => {
console.log('已更新');
// DOM已更新
});
onBeforeUnmount(() => {
console.log('卸载前');
// 卸载前清理
});
onUnmounted(() => {
console.log('已卸载');
// 最终清理
});
onErrorCaptured((err, instance, info) => {
console.error('捕获到错误:', err, info);
return false; // 停止传播
});
// 适用于被<KeepAlive>包裹的组件
onActivated(() => {
console.log('组件已激活');
});
onDeactivated(() => {
console.log('组件已停用');
});
}
};Composables - Reusable Composition Functions
组合式函数 - 可复用的组合逻辑
Simple Composable
简单组合式函数
typescript
// composables/useCounter.ts
import { ref, computed } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
function reset() {
count.value = initialValue;
}
return {
count: readonly(count),
doubled,
increment,
decrement,
reset
};
}
// Usage
<script setup lang="ts">
import { useCounter } from '@/composables/useCounter';
const { count, doubled, increment, decrement } = useCounter(10);
</script>typescript
// composables/useCounter.ts
import { ref, computed } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
const doubled = computed(() => count.value * 2);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
function reset() {
count.value = initialValue;
}
return {
count: readonly(count),
doubled,
increment,
decrement,
reset
};
}
// 使用示例
<script setup lang="ts">
import { useCounter } from '@/composables/useCounter';
const { count, doubled, increment, decrement } = useCounter(10);
</script>Advanced Composable with Side Effects
包含副作用的高级组合式函数
typescript
// composables/useFetch.ts
import { ref, unref, watchEffect } from 'vue';
import type { Ref } from 'vue';
export function useFetch<T>(url: Ref<string> | string) {
const data = ref<T | null>(null);
const error = ref<Error | null>(null);
const loading = ref(false);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch(unref(url));
if (!response.ok) throw new Error('Fetch failed');
data.value = await response.json();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
}
watchEffect(() => {
fetchData();
});
return {
data: readonly(data),
error: readonly(error),
loading: readonly(loading),
refetch: fetchData
};
}
// Usage
<script setup lang="ts">
import { ref } from 'vue';
import { useFetch } from '@/composables/useFetch';
const userId = ref('1');
const url = computed(() => `/api/users/${userId.value}`);
const { data, error, loading, refetch } = useFetch(url);
</script>typescript
// composables/useFetch.ts
import { ref, unref, watchEffect } from 'vue';
import type { Ref } from 'vue';
export function useFetch<T>(url: Ref<string> | string) {
const data = ref<T | null>(null);
const error = ref<Error | null>(null);
const loading = ref(false);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch(unref(url));
if (!response.ok) throw new Error('请求失败');
data.value = await response.json();
} catch (e) {
error.value = e as Error;
} finally {
loading.value = false;
}
}
watchEffect(() => {
fetchData();
});
return {
data: readonly(data),
error: readonly(error),
loading: readonly(loading),
refetch: fetchData
};
}
// 使用示例
<script setup lang="ts">
import { ref } from 'vue';
import { useFetch } from '@/composables/useFetch';
const userId = ref('1');
const url = computed(() => `/api/users/${userId.value}`);
const { data, error, loading, refetch } = useFetch(url);
</script>Composable with Cleanup
包含清理逻辑的组合式函数
typescript
// composables/useEventListener.ts
import { onMounted, onUnmounted } from 'vue';
export function useEventListener(
target: EventTarget,
event: string,
handler: (e: Event) => void
) {
onMounted(() => {
target.addEventListener(event, handler);
});
onUnmounted(() => {
target.removeEventListener(event, handler);
});
}
// Usage
<script setup lang="ts">
import { useEventListener } from '@/composables/useEventListener';
useEventListener(window, 'resize', () => {
console.log('Window resized');
});
</script>typescript
// composables/useEventListener.ts
import { onMounted, onUnmounted } from 'vue';
export function useEventListener(
target: EventTarget,
event: string,
handler: (e: Event) => void
) {
onMounted(() => {
target.addEventListener(event, handler);
});
onUnmounted(() => {
target.removeEventListener(event, handler);
});
}
// 使用示例
<script setup lang="ts">
import { useEventListener } from '@/composables/useEventListener';
useEventListener(window, 'resize', () => {
console.log('窗口已调整大小');
});
</script>Props and Emits in Composition API
Composition API中的Props与Emits
TypeScript Props
TypeScript Props
typescript
<script setup lang="ts">
interface Props {
title: string;
count?: number;
items: string[];
user: {
name: string;
email: string;
};
}
const props = withDefaults(defineProps<Props>(), {
count: 0
});
// Access props
console.log(props.title);
console.log(props.count);
// Destructuring loses reactivity - use toRefs
import { toRefs } from 'vue';
const { title, count } = toRefs(props);
</script>typescript
<script setup lang="ts">
interface Props {
title: string;
count?: number;
items: string[];
user: {
name: string;
email: string;
};
}
const props = withDefaults(defineProps<Props>(), {
count: 0
});
// 访问props
console.log(props.title);
console.log(props.count);
// 直接解构会丢失响应性 - 使用toRefs
import { toRefs } from 'vue';
const { title, count } = toRefs(props);
</script>TypeScript Emits
TypeScript Emits
typescript
<script setup lang="ts">
// Type-safe emits
const emit = defineEmits<{
update: [value: number];
delete: [];
change: [id: string, value: string];
}>();
function handleUpdate() {
emit('update', 42);
}
function handleChange(id: string, value: string) {
emit('change', id, value);
}
</script>typescript
<script setup lang="ts">
// 类型安全的Emits
const emit = defineEmits<{
update: [value: number];
delete: [];
change: [id: string, value: string];
}>();
function handleUpdate() {
emit('update', 42);
}
function handleChange(id: string, value: string) {
emit('change', id, value);
}
</script>Runtime Props Validation
运行时Props验证
typescript
<script setup lang="ts">
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0,
validator: (value: number) => value >= 0
},
status: {
type: String as PropType<'active' | 'inactive'>,
default: 'active'
}
});
</script>typescript
<script setup lang="ts">
const props = defineProps({
title: {
type: String,
required: true
},
count: {
type: Number,
default: 0,
validator: (value: number) => value >= 0
},
status: {
type: String as PropType<'active' | 'inactive'>,
default: 'active'
}
});
</script>Provide and Inject Patterns
Provide与Inject模式
Basic Provide/Inject
基础Provide/Inject
typescript
<!-- Parent Component -->
<script setup lang="ts">
import { provide, ref } from 'vue';
const theme = ref('dark');
const updateTheme = (newTheme: string) => {
theme.value = newTheme;
};
provide('theme', { theme, updateTheme });
</script>
<!-- Child Component (any depth) -->
<script setup lang="ts">
import { inject } from 'vue';
const themeContext = inject('theme');
// themeContext.theme
// themeContext.updateTheme('light')
</script>typescript
<!-- 父组件 -->
<script setup lang="ts">
import { provide, ref } from 'vue';
const theme = ref('dark');
const updateTheme = (newTheme: string) => {
theme.value = newTheme;
};
provide('theme', { theme, updateTheme });
</script>
<!-- 子组件(任意深度) -->
<script setup lang="ts">
import { inject } from 'vue';
const themeContext = inject('theme');
// themeContext.theme
// themeContext.updateTheme('light')
</script>Type-Safe Provide/Inject
类型安全的Provide/Inject
typescript
// keys.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Ref<string>;
updateTheme: (theme: string) => void;
}
export const ThemeKey: InjectionKey<ThemeContext> =
Symbol('theme');
// Provider
<script setup lang="ts">
import { provide, ref } from 'vue';
import { ThemeKey } from './keys';
const theme = ref('dark');
const updateTheme = (newTheme: string) => {
theme.value = newTheme;
};
provide(ThemeKey, { theme, updateTheme });
</script>
// Consumer
<script setup lang="ts">
import { inject } from 'vue';
import { ThemeKey } from './keys';
const theme = inject(ThemeKey);
// Fully typed!
</script>typescript
// keys.ts
import type { InjectionKey, Ref } from 'vue';
export interface ThemeContext {
theme: Ref<string>;
updateTheme: (theme: string) => void;
}
export const ThemeKey: InjectionKey<ThemeContext> =
Symbol('theme');
// 提供者
<script setup lang="ts">
import { provide, ref } from 'vue';
import { ThemeKey } from './keys';
const theme = ref('dark');
const updateTheme = (newTheme: string) => {
theme.value = newTheme;
};
provide(ThemeKey, { theme, updateTheme });
</script>
// 消费者
<script setup lang="ts">
import { inject } from 'vue';
import { ThemeKey } from './keys';
const theme = inject(ThemeKey);
// 完全类型化!
</script>Provide with Default Values
带默认值的Provide
typescript
<script setup lang="ts">
import { inject } from 'vue';
const theme = inject('theme', {
theme: ref('light'),
updateTheme: () => {}
});
// Or use factory function for reactive defaults
const config = inject('config', () => reactive({
locale: 'en',
timezone: 'UTC'
}), true); // true = treat as factory
</script>typescript
<script setup lang="ts">
import { inject } from 'vue';
const theme = inject('theme', {
theme: ref('light'),
updateTheme: () => {}
});
// 或使用工厂函数创建响应式默认值
const config = inject('config', () => reactive({
locale: 'en',
timezone: 'UTC'
}), true); // true = 视为工厂函数
</script>TypeScript with Composition API
Composition API与TypeScript
Component with Full Types
全类型组件
typescript
<script setup lang="ts">
import { ref, computed, type Ref, type ComputedRef } from 'vue';
interface User {
id: number;
name: string;
email: string;
}
interface Props {
userId: number;
}
interface Emits {
(e: 'update', user: User): void;
(e: 'delete', id: number): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const user: Ref<User | null> = ref(null);
const isLoading = ref(false);
const userName: ComputedRef<string> = computed(() =>
user.value?.name ?? 'Unknown'
);
async function loadUser() {
isLoading.value = true;
try {
const response = await fetch(`/api/users/${props.userId}`);
user.value = await response.json();
} finally {
isLoading.value = false;
}
}
function updateUser(updates: Partial<User>) {
if (user.value) {
user.value = { ...user.value, ...updates };
emit('update', user.value);
}
}
</script>typescript
<script setup lang="ts">
import { ref, computed, type Ref, type ComputedRef } from 'vue';
interface User {
id: number;
name: string;
email: string;
}
interface Props {
userId: number;
}
interface Emits {
(e: 'update', user: User): void;
(e: 'delete', id: number): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const user: Ref<User | null> = ref(null);
const isLoading = ref(false);
const userName: ComputedRef<string> = computed(() =>
user.value?.name ?? '未知'
);
async function loadUser() {
isLoading.value = true;
try {
const response = await fetch(`/api/users/${props.userId}`);
user.value = await response.json();
} finally {
isLoading.value = false;
}
}
function updateUser(updates: Partial<User>) {
if (user.value) {
user.value = { ...user.value, ...updates };
emit('update', user.value);
}
}
</script>Generic Composables
泛型组合式函数
typescript
// composables/useLocalStorage.ts
import { ref, watch, type Ref } from 'vue';
export function useLocalStorage<T>(
key: string,
defaultValue: T
): Ref<T> {
const data = ref<T>(defaultValue) as Ref<T>;
// Load from localStorage
const stored = localStorage.getItem(key);
if (stored) {
try {
data.value = JSON.parse(stored);
} catch (e) {
console.error('Failed to parse localStorage', e);
}
}
// Save to localStorage on change
watch(
data,
(newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
},
{ deep: true }
);
return data;
}
// Usage
const user = useLocalStorage<User>('user', { id: 0, name: '' });typescript
// composables/useLocalStorage.ts
import { ref, watch, type Ref } from 'vue';
export function useLocalStorage<T>(
key: string,
defaultValue: T
): Ref<T> {
const data = ref<T>(defaultValue) as Ref<T>;
// 从localStorage加载
const stored = localStorage.getItem(key);
if (stored) {
try {
data.value = JSON.parse(stored);
} catch (e) {
console.error('解析localStorage失败', e);
}
}
// 变更时保存到localStorage
watch(
data,
(newValue) => {
localStorage.setItem(key, JSON.stringify(newValue));
},
{ deep: true }
);
return data;
}
// 使用示例
const user = useLocalStorage<User>('user', { id: 0, name: '' });When to Use This Skill
何时使用该技能
Use vue-composition-api when building modern, production-ready
applications that require:
- Complex component logic that benefits from better organization
- Reusable logic across multiple components (composables)
- Better TypeScript integration and type inference
- Fine-grained reactivity control
- Large-scale applications requiring maintainability
- Migration from Vue 2 Options API to Vue 3
- Sharing stateful logic without mixins
在构建现代、生产级应用程序时使用vue-composition-api,尤其是当应用需要:
- 可从更优代码组织中获益的复杂组件逻辑
- 可在多个组件间复用的逻辑(组合式函数)
- 更好的TypeScript集成与类型推断
- 细粒度的响应性控制
- 可维护的大规模应用程序
- 从Vue 2 Options API迁移到Vue 3
- 无需混入即可共享有状态逻辑
Vue-Specific Best Practices
Vue专属最佳实践
- Prefer syntax - Cleaner, better performance, better types
<script setup> - Use composables for reusable logic - Extract to directory
composables/ - Use for primitives,
reffor objects - Unless you need to replace objectsreactive - Always use TypeScript - Better DX and fewer runtime errors
- Destructure reactive objects with - Preserve reactivity
toRefs - Use computed for derived state - Not methods in templates
- Cleanup side effects - Use for event listeners, timers
onUnmounted - Keep components focused - Extract complex logic to composables
- Use provide/inject for deep prop passing - Avoid prop drilling
- Name composables with prefix - Follow convention (useCounter, useFetch)
use
- 优先使用语法 - 更简洁、性能更优、类型支持更好
<script setup> - 使用组合式函数实现可复用逻辑 - 提取到目录
composables/ - 基本类型用ref,对象用reactive - 除非你需要替换整个对象
- 始终使用TypeScript - 更好的开发体验,更少的运行时错误
- 用toRefs解构响应式对象 - 保留响应性
- 使用计算属性处理派生状态 - 而非模板中的方法
- 清理副作用 - 使用处理事件监听器、定时器
onUnmounted - 保持组件聚焦 - 将复杂逻辑提取到组合式函数
- 使用provide/inject进行深层属性传递 - 避免属性透传
- 组合式函数以前缀命名 - 遵循约定(useCounter、useFetch)
use
Vue-Specific Pitfalls
Vue常见陷阱
- Destructuring props directly - Loses reactivity, use
toRefs(props) - Forgetting on refs - Common source of bugs
.value - Mutating props - Props are readonly, emit events instead
- Using reactive() for entire state - Can't replace, use ref for root
- Not cleaning up watchers - Memory leaks, store stop handle
- Accessing refs before mount - DOM refs are null in setup
- Overusing reactive() - Use ref for simple values
- Not using computed for derived state - Recalculates on every render
- Forgetting to return from setup() - Without
<script setup> - Mixing Options API and Composition API - Confusing, pick one
- 直接解构props - 丢失响应性,改用
toRefs(props) - 忘记ref的- 常见错误来源
.value - 修改props - props是只读的,应触发事件通知父组件
- 用reactive()管理整个状态 - 无法整体替换,根状态用ref
- 未清理监听器 - 内存泄漏,保存stop句柄
- 挂载前访问ref - setup中DOM ref为null
- 过度使用reactive() - 简单值用ref
- 未用计算属性处理派生状态 - 每次渲染都会重新计算
- setup()未返回值 - 未使用时需注意
<script setup> - 混合Options API与Composition API - 易混淆,选择一种即可
Common Patterns
常见模式
Form Handling
表单处理
typescript
<script setup lang="ts">
import { reactive, computed } from 'vue';
interface FormData {
name: string;
email: string;
password: string;
}
interface FormErrors {
name?: string;
email?: string;
password?: string;
}
const form = reactive<FormData>({
name: '',
email: '',
password: ''
});
const errors = reactive<FormErrors>({});
const isValid = computed(() =>
Object.keys(errors).length === 0 &&
form.name && form.email && form.password
);
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validate() {
if (!form.name) {
errors.name = 'Name is required';
} else {
delete errors.name;
}
if (!validateEmail(form.email)) {
errors.email = 'Invalid email';
} else {
delete errors.email;
}
if (form.password.length < 8) {
errors.password = 'Password must be 8+ characters';
} else {
delete errors.password;
}
}
async function submit() {
validate();
if (!isValid.value) return;
// Submit form
await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(form)
});
}
</script>typescript
<script setup lang="ts">
import { reactive, computed } from 'vue';
interface FormData {
name: string;
email: string;
password: string;
}
interface FormErrors {
name?: string;
email?: string;
password?: string;
}
const form = reactive<FormData>({
name: '',
email: '',
password: ''
});
const errors = reactive<FormErrors>({});
const isValid = computed(() =>
Object.keys(errors).length === 0 &&
form.name && form.email && form.password
);
function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
function validate() {
if (!form.name) {
errors.name = '姓名为必填项';
} else {
delete errors.name;
}
if (!validateEmail(form.email)) {
errors.email = '无效邮箱';
} else {
delete errors.email;
}
if (form.password.length < 8) {
errors.password = '密码长度至少8位';
} else {
delete errors.password;
}
}
async function submit() {
validate();
if (!isValid.value) return;
// 提交表单
await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(form)
});
}
</script>Async Data Loading
异步数据加载
typescript
<script setup lang="ts">
import { ref, onMounted } from 'vue';
interface Data {
id: number;
title: string;
}
const data = ref<Data[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('Failed to fetch');
data.value = await response.json();
} catch (e) {
error.value = (e as Error).message;
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchData();
});
</script>
<template>
<div>
<div v-if="loading">Loading...</div>
<div v-else-if="error">Error: {{ error }}</div>
<div v-else>
<div v-for="item in data" :key="item.id">
{{ item.title }}
</div>
</div>
</div>
</template>typescript
<script setup lang="ts">
import { ref, onMounted } from 'vue';
interface Data {
id: number;
title: string;
}
const data = ref<Data[]>([]);
const loading = ref(false);
const error = ref<string | null>(null);
async function fetchData() {
loading.value = true;
error.value = null;
try {
const response = await fetch('/api/data');
if (!response.ok) throw new Error('请求失败');
data.value = await response.json();
} catch (e) {
error.value = (e as Error).message;
} finally {
loading.value = false;
}
}
onMounted(() => {
fetchData();
});
</script>
<template>
<div>
<div v-if="loading">加载中...</div>
<div v-else-if="error">错误:{{ error }}</div>
<div v-else>
<div v-for="item in data" :key="item.id">
{{ item.title }}
</div>
</div>
</div>
</template>