Skip to content

Vue Composition API

概述

Composition API 是 Vue 3 的核心特性,提供了更灵活的逻辑组织和复用方式。

一、Options API vs Composition API

特性Options APIComposition API
组织方式按选项分类按功能组织
逻辑复用mixinscomposables
TypeScript支持较差原生支持
代码组织分散集中

二、setup 函数

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

export default {
  props: {
    initialCount: Number
  },
  setup(props, { attrs, slots, emit, expose }) {
    // 响应式数据
    const count = ref(props.initialCount || 0);
    const state = reactive({ name: 'Vue' });
    
    // 计算属性
    const double = computed(() => count.value * 2);
    
    // 方法
    const increment = () => count.value++;
    
    // 生命周期
    onMounted(() => {
      console.log('mounted');
    });
    
    // 暴露给模板
    return { count, double, increment, state };
  }
}

setup 语法糖

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

const props = defineProps({
  initialCount: Number
});

const emit = defineEmits(['update']);

const count = ref(props.initialCount || 0);
const double = computed(() => count.value * 2);

const increment = () => {
  count.value++;
  emit('update', count.value);
};

// 暴露给父组件 ref
defineExpose({ count, increment });
</script>

三、响应式 API

ref vs reactive

javascript
// ref - 基本类型 + 对象
const count = ref(0);
count.value++; // 需要 .value

const user = ref({ name: 'Vue' });
user.value.name = 'React'; // 对象也需要 .value

// reactive - 仅对象
const state = reactive({ count: 0 });
state.count++; // 直接访问

// 解构失去响应式
const { count } = reactive({ count: 0 }); // ❌ 失去响应式
const { count } = toRefs(reactive({ count: 0 })); // ✅ 保持响应式

其他响应式 API

javascript
// toRef - 创建单个 ref
const state = reactive({ count: 0 });
const countRef = toRef(state, 'count');

// toRefs - 解构保持响应式
const { count, name } = toRefs(state);

// unref - 获取值
const val = unref(maybeRef); // ref ? ref.value : ref

// isRef / isReactive / isReadonly
isRef(count); // true

// shallowRef / shallowReactive - 浅层响应式
const state = shallowReactive({ nested: { a: 1 } });
state.nested = { a: 2 }; // 触发更新
state.nested.a = 3;      // 不触发更新

// readonly - 只读
const ro = readonly(state);

// toRaw - 获取原始对象
const raw = toRaw(state);

四、Composables (可组合函数)

基础示例

javascript
// composables/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue';

export function useMouse() {
  const x = ref(0);
  const y = ref(0);
  
  function update(e) {
    x.value = e.pageX;
    y.value = e.pageY;
  }
  
  onMounted(() => window.addEventListener('mousemove', update));
  onUnmounted(() => window.removeEventListener('mousemove', update));
  
  return { x, y };
}

// 使用
import { useMouse } from './composables/useMouse';

const { x, y } = useMouse();

异步数据获取

javascript
// composables/useFetch.js
import { ref, watchEffect, toValue } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(true);
  
  watchEffect(async () => {
    loading.value = true;
    data.value = null;
    error.value = null;
    
    try {
      const res = await fetch(toValue(url)); // 支持 ref
      data.value = await res.json();
    } catch (e) {
      error.value = e;
    } finally {
      loading.value = false;
    }
  });
  
  return { data, error, loading };
}

// 使用
const url = ref('/api/users');
const { data, loading } = useFetch(url);

// 更改 url 会自动重新请求
url.value = '/api/posts';

命名规范

javascript
// ✅ use 前缀
export function useCounter() {}
export function useMouse() {}
export function useFetch() {}

// ✅ 返回 ref 而非 reactive
export function useCounter() {
  const count = ref(0);
  return { count }; // ✅ 解构保持响应式
}

// ❌ 返回 reactive
export function useCounter() {
  return reactive({ count: 0 }); // ❌ 解构失去响应式
}

五、生命周期钩子

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

// 对应关系
// beforeCreate/created → setup()
// beforeMount → onBeforeMount
// mounted → onMounted
// beforeUpdate → onBeforeUpdate
// updated → onUpdated
// beforeUnmount → onBeforeUnmount
// unmounted → onUnmounted

onMounted(() => {
  console.log('组件挂载完成');
});

onUnmounted(() => {
  console.log('组件卸载');
});

六、依赖注入

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

const theme = ref('dark');
provide('theme', theme);
provide('updateTheme', (val) => theme.value = val);

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

const theme = inject('theme', 'light'); // 第二个参数是默认值
const updateTheme = inject('updateTheme');

面试高频题

Q1: setup 什么时候执行?

在 beforeCreate 之前执行,此时组件实例还未创建。

Q2: 为什么 ref 需要 .value?

JavaScript 基本类型是值传递,无法追踪。ref 用对象包装,通过 getter/setter 拦截访问。

Q3: Composables vs Mixins?

ComposablesMixins
显式导入隐式合并
无命名冲突可能冲突
类型推断好类型推断差

前端面试知识库