Skip to main content

组合式基础语法

Vue 3 组合式 API 基础语法详解

Vue 3 引入的组合式 API 是一种全新的代码组织方式,它通过 setup() 函数或 <script setup> 语法糖来组合组件逻辑。组合式 API 解决了选项式 API 在大型组件中逻辑分散的问题,使代码更具可读性和可维护性。

一、基本概念与 Setup 函数

1. Setup 函数基础

setup() 是组合式 API 的入口点,在组件初始化时执行,早于所有生命周期钩子。

import { ref, reactive, computed } from 'vue';

export default {
setup() {
// 响应式数据
const count = ref(0); // ref 用于基本类型
const user = reactive({ name: 'John', age: 30 }); // reactive 用于对象/数组

// 计算属性
const doubleCount = computed(() => count.value * 2);

// 方法
const increment = () => {
count.value++;
};

// 生命周期钩子
onMounted(() => {
console.log('Component mounted');
});

// 返回数据和方法供模板使用
return {
count,
user,
doubleCount,
increment
};
}
};

2. <script setup> 语法糖

Vue 3.2+ 引入的更简洁写法,无需显式 setup()return

<template>
<div>
<p>{{ count }}</p>
<button @click="increment">+1</button>
</div>
</template>

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

// 响应式数据
const count = ref(0);

// 方法
const increment = () => {
count.value++;
};

// 生命周期钩子
onMounted(() => {
console.log('Component mounted with <script setup>');
});
</script>

二、响应式 API

1. ref 和 reactive

  • ref:创建任意类型的响应式数据,通过 .value 访问
  • reactive:创建对象/数组的深层响应式代理
import { ref, reactive, toRefs } from 'vue';

// ref 示例
const message = ref('Hello');
console.log(message.value); // 访问值
message.value = 'World'; // 修改值

// reactive 示例
const state = reactive({
count: 0,
user: { name: 'Alice' }
});

// 解构响应式对象(会丢失响应性)
const { count } = state; // 非响应式
count++; // 不会更新视图

// 使用 toRefs 保持响应性
const { count } = toRefs(state); // 响应式
count.value++; // 会更新视图

2. computed

创建计算属性,支持 getter 和 setter。

import { ref, computed } from 'vue';

const firstName = ref('John');
const lastName = ref('Doe');

// 只读计算属性
const fullName = computed(() => {
return `${firstName.value} ${lastName.value}`;
});

// 可写计算属性
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`;
},
set(newValue) {
const [first, last] = newValue.split(' ');
firstName.value = first;
lastName.value = last;
}
});

3. watch 和 watchEffect

监听响应式数据变化。

import { ref, watch, watchEffect } from 'vue';

const count = ref(0);
const message = ref('');

// 基本 watch
watch(count, (newVal, oldVal) => {
console.log(`Count changed from ${oldVal} to ${newVal}`);
});

// 监听多个数据源
watch([count, message], ([newCount, newMessage], [oldCount, oldMessage]) => {
console.log(`Count: ${newCount}, Message: ${newMessage}`);
});

// watchEffect:自动追踪依赖
watchEffect(() => {
console.log(`Count is: ${count.value}`); // 自动追踪 count
});

三、生命周期钩子

在组合式 API 中使用生命周期钩子,只需导入对应的函数。

import { 
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured
} from 'vue';

export default {
setup() {
// 组件挂载前
onBeforeMount(() => {
console.log('Before mount');
});

// 组件挂载后
onMounted(() => {
console.log('Mounted');
// 可以访问 DOM
});

// 组件更新前
onBeforeUpdate(() => {
console.log('Before update');
});

// 组件更新后
onUpdated(() => {
console.log('Updated');
});

// 组件卸载前
onBeforeUnmount(() => {
console.log('Before unmount');
});

// 组件卸载后
onUnmounted(() => {
console.log('Unmounted');
// 清理工作(定时器、事件监听器等)
});

// 错误捕获
onErrorCaptured((err, instance, info) => {
console.error('Error captured:', err);
return false; // 阻止错误继续传播
});
}
};

四、提供 (Provide) 与注入 (Inject)

实现跨层级组件通信,父组件提供数据,子组件注入使用。

// 父组件
import { provide, ref } from 'vue';

export default {
setup() {
const theme = ref('light');

// 提供数据
provide('theme', theme);

const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
};

return {
toggleTheme
};
}
};

// 深层子组件
import { inject } from 'vue';

export default {
setup() {
// 注入数据
const theme = inject('theme');

return {
theme
};
}
};

五、自定义组合函数 (Composables)

将可复用的逻辑提取到独立函数中,提高代码复用性。

// useCounter.js - 自定义组合函数
import { ref, computed, onMounted, onUnmounted } from 'vue';

export function useCounter(initialValue = 0) {
const count = ref(initialValue);

const increment = () => {
count.value++;
};

const decrement = () => {
count.value--;
};

const doubleCount = computed(() => count.value * 2);

// 生命周期钩子也可在组合函数中使用
onMounted(() => {
console.log('Counter initialized');
});

onUnmounted(() => {
console.log('Counter destroyed');
});

return {
count,
increment,
decrement,
doubleCount
};
}

// 在组件中使用
import { useCounter } from './useCounter';

export default {
setup() {
const { count, increment, doubleCount } = useCounter(5);

return {
count,
increment,
doubleCount
};
}
};

六、异步操作与 Promise

在组合式 API 中处理异步逻辑。

import { ref, onMounted } from 'vue';

export default {
setup() {
const posts = ref([]);
const loading = ref(false);
const error = ref(null);

const fetchPosts = async () => {
loading.value = true;
error.value = null;

try {
const response = await fetch('https://api.example.com/posts');
if (!response.ok) {
throw new Error('Failed to fetch posts');
}
posts.value = await response.json();
} catch (err) {
error.value = err.message;
} finally {
loading.value = false;
}
};

onMounted(fetchPosts);

return {
posts,
loading,
error,
fetchPosts
};
}
};

七、组件通信

1. 自定义事件

在组合式 API 中触发自定义事件。

// 子组件
import { defineEmits } from 'vue';

export default {
setup(props, { emit }) {
// 或者使用 defineEmits 宏(<script setup> 中)
// const emit = defineEmits(['submit']);

const handleClick = () => {
emit('submit', 'Form data');
};

return {
handleClick
};
}
};

// 父组件
<template>
<child-component @submit="handleSubmit" />
</template>

<script setup>
const handleSubmit = (data) => {
console.log('Received data:', data);
};
</script>

2. props

在组合式 API 中定义和使用 props。

// 子组件
import { defineProps } from 'vue';

export default {
props: {
message: String,
count: {
type: Number,
default: 0
}
},
setup(props) {
// 使用 defineProps 宏(<script setup> 中)
// const props = defineProps({
// message: String,
// count: Number
// });

console.log(props.message);

return {};
}
};

八、响应式原理与高级 API

1. readonly

创建只读的响应式代理。

import { reactive, readonly } from 'vue';

const state = reactive({ count: 0 });
const readOnlyState = readonly(state);

// 修改原始状态会更新只读代理
state.count++; // 正确

// 直接修改只读代理会失败(开发环境警告,生产环境静默失败)
readOnlyState.count++; // 错误

2. toRef

创建一个 ref,保持对原始属性的响应式连接。

import { reactive, toRef } from 'vue';

const state = reactive({
name: 'John',
age: 30
});

// 创建独立的 ref,但保持响应性
const nameRef = toRef(state, 'name');

// 修改原始对象或 ref 都会影响对方
state.name = 'Jane'; // nameRef.value 变为 'Jane'
nameRef.value = 'Bob'; // state.name 变为 'Bob'