Vue前端

Vue 3 组合式 API 实战

2024-10-05
前端

为什么选择组合式 API?

Vue 3 的组合式 API(Composition API)是一种新的组件编写方式,相比 Options API,它提供了更好的代码组织方式和逻辑复用能力。

基础对比

Options API

<script>
export default {
  data() {
    return {
      count: 0,
      name: ''
    };
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  },
  watch: {
    count(newVal) {
      console.log('count changed:', newVal);
    }
  }
};
</script>

Composition API

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

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

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

function increment() {
  count.value++;
}

watch(count, (newVal) => {
  console.log('count changed:', newVal);
});
</script>

逻辑复用:自定义 Hooks

组合式 API 最大的优势之一是逻辑复用。

创建可复用的逻辑

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

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

使用自定义 Hook

<script setup>
import { useCounter } from './useCounter';

const { count, doubleCount, increment, decrement } = useCounter(10);
</script>

<template>
  <div>
    <p>Count: {{ count }}</p>
    <p>Double: {{ doubleCount }}</p>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

常用 Hooks 示例

useFetch

import { ref, watchEffect } from 'vue';

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

useLocalStorage

import { ref, watch } from 'vue';

export function useLocalStorage(key, defaultValue) {
  const stored = localStorage.getItem(key);
  const initial = stored ? JSON.parse(stored) : defaultValue;
  const value = ref(initial);
  
  watch(value, (newVal) => {
    localStorage.setItem(key, JSON.stringify(newVal));
  }, { deep: true });
  
  return value;
}

useDebounce

import { ref, watch } from 'vue';

export function useDebounce(value, delay = 300) {
  const debouncedValue = ref(value.value);
  let timeout = null;
  
  watch(value, (newVal) => {
    clearTimeout(timeout);
    timeout = setTimeout(() => {
      debouncedValue.value = newVal;
    }, delay);
  });
  
  return debouncedValue;
}

代码组织

按功能组织

src/
├── composables/
│   ├── useAuth.js
│   ├── useFetch.js
│   ├── useCounter.js
│   └── index.js
├── components/
│   ├── UserCard.vue
│   └── Counter.vue
└── views/
    ├── Home.vue
    └── Profile.vue

按领域组织

src/
├── features/
│   ├── auth/
│   │   ├── useAuth.js
│   │   ├── useLogin.js
│   │   └── components/
│   │       └── LoginForm.vue
│   └── counter/
│       ├── useCounter.js
│       └── components/
│           └── Counter.vue

最佳实践

1. 使用 `<script setup>`

<script setup>
// 推荐的写法
import { ref } from 'vue';
const count = ref(0);
</script>

2. 合理使用 ref 和 reactive

// 基本类型用 ref
const count = ref(0);

// 对象类型可以用 reactive
const user = reactive({
  name: 'John',
  age: 30
});

// 或者统一用 ref
const user = ref({
  name: 'John',
  age: 30
});

3. 使用 toRefs 解构

import { reactive, toRefs } from 'vue';

const state = reactive({
  count: 0,
  name: ''
});

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

迁移建议

1. 渐进式迁移

不需要一次性迁移所有组件,可以:

  • 新组件使用 Composition API
  • 旧组件逐步迁移
  • 两种 API 可以共存
  • 2. 迁移步骤

  • data 改为 refreactive
  • computed 改为 computed()
  • methods 改为普通函数
  • watch 改为 watch()
  • 提取可复用逻辑为自定义 Hook
  • 总结

    组合式 API 的优势:

  • **更好的代码组织**:相关逻辑放在一起
  • **更好的逻辑复用**:通过自定义 Hook
  • **更好的类型推导**:对 TypeScript 更友好
  • **更小的打包体积**:Tree-shaking 更友好
  • 记住,Composition API 不是要取代 Options API,而是提供更多的选择。根据项目规模和团队习惯选择合适的 API。