Skip to content

组合式API

组合式API(Composition API)是Vue 3中引入的新特性,它提供了一种新的组织组件逻辑的方式,使代码更加可维护和可复用。本文将详细介绍Vue 3的组合式API及其使用场景。

组合式API的背景

在Vue 2中,我们使用选项式API(Options API)来组织组件逻辑,将代码分为data、methods、computed等选项。这种方式对于小型组件来说非常直观,但对于大型组件来说,逻辑会分散在不同的选项中,导致代码难以维护和复用。

组合式API的出现就是为了解决这个问题,它允许我们按照逻辑关注点组织代码,而不是按照选项类型。

组合式API的基本使用

1. setup函数

setup函数是组合式API的入口点,它在组件创建之前执行,替代了Vue 2中的beforeCreatecreated钩子。

使用方式

vue
<template>
  <div>
    <h2>{{ title }}</h2>
    <p>Count: {{ count }}</p>
    <button @click="increment">增加计数</button>
  </div>
</template>

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

export default {
  setup() {
    // 响应式数据
    const count = ref(0);
    
    // 计算属性
    const title = computed(() => `Count: ${count.value}`);
    
    // 方法
    const increment = () => {
      count.value++;
    };
    
    // 生命周期钩子
    onMounted(() => {
      console.log('Component mounted');
    });
    
    // 返回需要暴露给模板的变量和方法
    return {
      count,
      title,
      increment
    };
  }
};
</script>

注意:在setup函数中,this关键字不可用,因为setup函数在组件实例创建之前执行。

2. 响应式API

组合式API提供了以下响应式API:

2.1 ref

ref用于创建响应式的基本类型数据(String、Number、Boolean等)。

使用方式

javascript
import { ref } from 'vue';

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

// 访问响应式数据
console.log(count.value); // 0

// 修改响应式数据
count.value++; // 1

特点

  • ref返回一个包含value属性的对象。
  • 在模板中使用ref创建的响应式数据时,Vue会自动解包,不需要使用.value
  • setup函数中使用ref创建的响应式数据时,需要使用.value访问和修改。

2.2 reactive

reactive用于创建响应式的对象类型数据。

使用方式

javascript
import { reactive } from 'vue';

// 创建响应式对象
const user = reactive({
  name: 'John',
  age: 30
});

// 访问响应式对象的属性
console.log(user.name); // John

// 修改响应式对象的属性
user.age = 31;

特点

  • reactive返回一个响应式的代理对象。
  • 不能直接替换整个响应式对象,否则会失去响应性。
  • 可以使用toRefs将响应式对象转换为ref对象。

2.3 toRefs

toRefs用于将响应式对象转换为ref对象的集合,这样在解构时不会失去响应性。

使用方式

javascript
import { reactive, toRefs } from 'vue';

// 创建响应式对象
const user = reactive({
  name: 'John',
  age: 30
});

// 转换为ref对象的集合
const userRefs = toRefs(user);

// 解构
const { name, age } = userRefs;

// 访问和修改
console.log(name.value); // John
age.value = 31;

2.4 computed

computed用于创建计算属性,计算属性会缓存计算结果,只有当依赖项变化时才会重新计算。

使用方式

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

const count = ref(0);

// 创建计算属性
const doubleCount = computed(() => {
  return count.value * 2;
});

// 访问计算属性
console.log(doubleCount.value); // 0

// 修改依赖项
count.value = 1;
console.log(doubleCount.value); // 2

特点

  • computed返回一个只读的ref对象。
  • 计算属性的函数应该是纯函数,不应该修改状态。

2.5 watch

watch用于监听响应式数据的变化。

使用方式

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

const count = ref(0);

// 监听单个数据
watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`);
});

// 监听多个数据
watch([count1, count2], ([newCount1, newCount2], [oldCount1, oldCount2]) => {
  console.log('Counts changed');
});

// 监听对象的属性
watch(
  () => user.name,
  (newName, oldName) => {
    console.log(`Name changed from ${oldName} to ${newName}`);
  }
);

// 深度监听
watch(
  user,
  (newUser, oldUser) => {
    console.log('User changed');
  },
  { deep: true }
);

2.6 watchEffect

watchEffect用于自动追踪响应式依赖的变化,当依赖项变化时,会重新执行回调函数。

使用方式

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

const count = ref(0);
const name = ref('John');

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

// 修改依赖项
count.value = 1; // 会触发回调
name.value = 'Jane'; // 会触发回调

// 停止监听
stop();

特点

  • watchEffect会立即执行一次回调函数,然后自动追踪依赖。
  • watchEffect返回一个停止函数,可以手动停止监听。

3. 生命周期钩子

组合式API提供了以下生命周期钩子:

组合式API钩子选项式API钩子描述
onBeforeMountbeforeMount组件挂载之前执行
onMountedmounted组件挂载之后执行
onBeforeUpdatebeforeUpdate组件更新之前执行
onUpdatedupdated组件更新之后执行
onBeforeUnmountbeforeDestroy组件卸载之前执行
onUnmounteddestroyed组件卸载之后执行
onActivatedactivatedkeep-alive组件激活时执行
onDeactivateddeactivatedkeep-alive组件停用时执行
onErrorCapturederrorCaptured捕获子组件错误时执行
onRenderTracked-响应式依赖被收集时执行(仅开发模式)
onRenderTriggered-响应式依赖被触发时执行(仅开发模式)

使用方式

javascript
import { onMounted, onBeforeUnmount } from 'vue';

setup() {
  onMounted(() => {
    console.log('Component mounted');
  });
  
  onBeforeUnmount(() => {
    console.log('Component before unmount');
  });
}

4. 依赖注入

组合式API提供了provideinject函数,用于跨组件传递数据。

使用方式

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

export default {
  setup() {
    const message = ref('Hello from parent');
    
    // 提供数据
    provide('message', message);
    
    return {};
  }
};

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

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

组合式API的优势

1. 更好的代码组织

组合式API允许我们按照逻辑关注点组织代码,而不是按照选项类型。这样可以使代码更加清晰易读,便于维护。

示例

javascript
// 选项式API
export default {
  data() {
    return {
      count: 0,
      user: {}
    };
  },
  methods: {
    increment() {
      this.count++;
    },
    fetchUser() {
      // 获取用户数据
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    }
  },
  mounted() {
    this.fetchUser();
  }
};

// 组合式API
import { ref, computed, onMounted } from 'vue';

export default {
  setup() {
    // 计数逻辑
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);
    const increment = () => count.value++;
    
    // 用户逻辑
    const user = ref({});
    const fetchUser = async () => {
      // 获取用户数据
    };
    
    // 生命周期
    onMounted(() => {
      fetchUser();
    });
    
    return {
      count,
      doubleCount,
      increment,
      user
    };
  }
};

2. 更好的代码复用

组合式API允许我们将相关的逻辑提取到单独的函数中,这些函数可以在多个组件中复用。

示例

javascript
// composables/useCounter.js
import { ref, computed } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const doubleCount = computed(() => count.value * 2);
  const increment = () => count.value++;
  const decrement = () => count.value--;
  
  return {
    count,
    doubleCount,
    increment,
    decrement
  };
}

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

export default {
  setup() {
    const { count, doubleCount, increment, decrement } = useCounter(10);
    
    return {
      count,
      doubleCount,
      increment,
      decrement
    };
  }
};

3. 更好的类型推断

组合式API提供了更好的TypeScript类型推断支持,因为它使用了函数式的方式组织代码,使得TypeScript能够更好地推断类型。

示例

typescript
// composables/useCounter.ts
import { ref, computed, Ref } from 'vue';

export function useCounter(initialValue: number = 0) {
  const count: Ref<number> = ref(initialValue);
  const doubleCount: Ref<number> = computed(() => count.value * 2);
  const increment = () => count.value++;
  const decrement = () => count.value--;
  
  return {
    count,
    doubleCount,
    increment,
    decrement
  };
}

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

export default {
  setup() {
    const { count, doubleCount, increment, decrement } = useCounter(10);
    // TypeScript会自动推断出count的类型为Ref<number>
    
    return {
      count,
      doubleCount,
      increment,
      decrement
    };
  }
};

4. 更小的打包体积

组合式API支持Tree Shaking,只打包使用到的API,减少了打包体积。

组合式API的实际应用

1. 状态管理

使用组合式API可以创建简单的状态管理方案,而不需要使用Vuex或Pinia。

示例

javascript
// composables/useStore.js
import { ref, computed } from 'vue';

const count = ref(0);
const user = ref({});

export function useStore() {
  const increment = () => count.value++;
  const setUser = (newUser) => user.value = newUser;
  const doubleCount = computed(() => count.value * 2);
  
  return {
    count,
    user,
    increment,
    setUser,
    doubleCount
  };
}

2. 表单处理

使用组合式API可以创建可复用的表单处理逻辑。

示例

javascript
// composables/useForm.js
import { ref, computed } from 'vue';

export function useForm(initialValues = {}) {
  const values = ref({ ...initialValues });
  const errors = ref({});
  const touched = ref({});
  
  const validate = () => {
    errors.value = {};
    // 验证逻辑
    return Object.keys(errors.value).length === 0;
  };
  
  const handleSubmit = (onSubmit) => {
    touched.value = Object.fromEntries(
      Object.keys(values.value).map(key => [key, true])
    );
    
    if (validate()) {
      onSubmit(values.value);
    }
  };
  
  const handleChange = (key, value) => {
    values.value[key] = value;
    if (touched.value[key]) {
      // 验证单个字段
    }
  };
  
  const handleBlur = (key) => {
    touched.value[key] = true;
    // 验证单个字段
  };
  
  const isError = (key) => touched.value[key] && errors.value[key];
  
  return {
    values,
    errors,
    touched,
    validate,
    handleSubmit,
    handleChange,
    handleBlur,
    isError
  };
}

3. 网络请求

使用组合式API可以创建可复用的网络请求逻辑。

示例

javascript
// composables/useFetch.js
import { ref, onMounted, watch } from 'vue';

export function useFetch(url, options = {}) {
  const data = ref(null);
  const loading = ref(false);
  const error = ref(null);
  
  const fetchData = async () => {
    loading.value = true;
    error.value = null;
    
    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      data.value = await response.json();
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  };
  
  onMounted(() => {
    fetchData();
  });
  
  // 监听url变化
  watch(url, () => {
    fetchData();
  });
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  };
}

组合式API与选项式API的对比

特性组合式API选项式API
代码组织按逻辑关注点组织按选项类型组织
代码复用支持逻辑复用,可提取为单独的函数支持mixins,但可能导致命名冲突
类型推断更好的TypeScript类型推断类型推断较差
打包体积支持Tree Shaking,打包体积更小不支持Tree Shaking,打包体积较大
学习曲线学习曲线较陡学习曲线较平缓
适用场景大型组件,需要逻辑复用的场景小型组件,逻辑简单的场景

组合式API的最佳实践

1. 按逻辑关注点组织代码

使用组合式API时,应该按照逻辑关注点组织代码,将相关的状态、计算属性和方法放在一起。

2. 提取可复用的逻辑

将可复用的逻辑提取为单独的composables函数,以便在多个组件中复用。

3. 使用TypeScript

组合式API与TypeScript配合使用可以获得更好的类型推断支持,提高代码的可维护性。

4. 合理使用响应式API

  • 对于基本类型数据,使用ref
  • 对于对象类型数据,使用reactive
  • 对于需要解构的响应式对象,使用toRefs

5. 避免在setup函数中使用this

setup函数中,this关键字不可用,应该使用组合式API提供的响应式API和生命周期钩子。

6. 合理使用watch和watchEffect

  • 对于需要明确知道新旧值的场景,使用watch
  • 对于需要自动追踪依赖的场景,使用watchEffect

面试常见问题

1. 什么是组合式API?它解决了什么问题?

组合式API是Vue 3中引入的新特性,它提供了一种新的组织组件逻辑的方式,使代码更加可维护和可复用。

解决的问题

  • 代码组织:选项式API按选项类型组织代码,导致大型组件的逻辑分散,难以维护。
  • 代码复用:选项式API通过mixins实现代码复用,但可能导致命名冲突。
  • 类型推断:选项式API的TypeScript类型推断较差。
  • 打包体积:选项式API不支持Tree Shaking,打包体积较大。

2. 组合式API与选项式API的区别是什么?

特性组合式API选项式API
代码组织按逻辑关注点组织按选项类型组织
代码复用支持逻辑复用,可提取为单独的函数支持mixins,但可能导致命名冲突
类型推断更好的TypeScript类型推断类型推断较差
打包体积支持Tree Shaking,打包体积更小不支持Tree Shaking,打包体积较大
学习曲线学习曲线较陡学习曲线较平缓

3. setup函数的作用是什么?它在组件生命周期中的位置是什么?

setup函数是组合式API的入口点,它在组件创建之前执行,替代了Vue 2中的beforeCreatecreated钩子。

作用

  • 初始化响应式数据。
  • 定义方法和计算属性。
  • 注册生命周期钩子。
  • 返回需要暴露给模板的变量和方法。

4. 组合式API提供了哪些响应式API?

组合式API提供了以下响应式API:

  • ref:用于创建响应式的基本类型数据。
  • reactive:用于创建响应式的对象类型数据。
  • toRefs:用于将响应式对象转换为ref对象的集合。
  • computed:用于创建计算属性。
  • watch:用于监听响应式数据的变化。
  • watchEffect:用于自动追踪响应式依赖的变化。

5. ref和reactive的区别是什么?

特性refreactive
适用类型基本类型和对象类型仅对象类型
访问方式需要使用.value访问和修改直接访问和修改属性
模板使用自动解包,不需要.value直接使用
解构行为解构后仍保持响应性解构后失去响应性,需要使用toRefs

6. watch和watchEffect的区别是什么?

特性watchwatchEffect
执行时机首次不会执行,只有当监听的数据变化时才执行首次会立即执行,然后当依赖变化时执行
依赖追踪需要明确指定监听的数据自动追踪回调函数中使用的响应式数据
新旧值可以获取新旧值无法获取新旧值
适用场景需要明确知道数据变化前后值的场景需要自动追踪依赖的场景

7. 如何在组合式API中使用生命周期钩子?

在组合式API中,生命周期钩子需要从vue中导入,然后在setup函数中使用:

javascript
import { onMounted, onBeforeUnmount } from 'vue';

export default {
  setup() {
    onMounted(() => {
      console.log('Component mounted');
    });
    
    onBeforeUnmount(() => {
      console.log('Component before unmount');
    });
    
    return {};
  }
};

8. 如何在组合式API中实现依赖注入?

在组合式API中,依赖注入需要使用provideinject函数:

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

export default {
  setup() {
    const message = ref('Hello from parent');
    
    provide('message', message);
    
    return {};
  }
};

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

export default {
  setup() {
    const message = inject('message');
    
    return {
      message
    };
  }
};

9. 组合式API的优势是什么?

组合式API的优势:

  • 更好的代码组织:按逻辑关注点组织代码,使代码更加清晰易读。
  • 更好的代码复用:支持逻辑复用,可提取为单独的函数。
  • 更好的类型推断:与TypeScript配合使用可以获得更好的类型推断支持。
  • 更小的打包体积:支持Tree Shaking,打包体积更小。

10. 组合式API的适用场景是什么?

组合式API适用于以下场景:

  • 大型组件:需要按逻辑关注点组织代码的大型组件。
  • 需要逻辑复用的场景:多个组件需要共享相同的逻辑。
  • 使用TypeScript的场景:需要更好的TypeScript类型推断支持。
  • 对打包体积有要求的场景:需要更小的打包体积。

总结

组合式API是Vue 3中引入的重要特性,它提供了一种新的组织组件逻辑的方式,使代码更加可维护和可复用。通过合理使用组合式API,可以提高代码的质量和开发效率。

组合式API与选项式API并不是互斥的,它们可以在同一个项目中混合使用。对于小型组件,选项式API可能更加直观;对于大型组件,组合式API可能更加适合。

在实际开发中,应该根据具体的场景选择合适的API风格,以充分发挥Vue的优势。

好好学习,天天向上