Vue 3 Composition API最佳实践:从基础语法到复杂组件状态管理完整教程

Will825
Will825 2026-02-08T19:13:01+08:00
0 0 1

引言

Vue 3 的发布带来了革命性的变化,其中最引人注目的就是 Composition API 的引入。相比于传统的 Options API,Composition API 提供了更灵活、更强大的组件状态管理方式。本文将深入探讨 Vue 3 Composition API 的核心概念、使用技巧以及最佳实践,帮助开发者构建现代化的前端应用。

什么是 Composition API

基础概念

Composition API 是 Vue 3 中引入的一种新的组件状态管理方式,它允许我们以函数的形式组织和复用逻辑代码。与传统的 Options API(如 data、methods、computed、watch 等选项)不同,Composition API 更加灵活,能够更好地处理复杂的组件逻辑。

与 Options API 的对比

在 Vue 2 中,我们通常使用 Options API 来组织组件逻辑:

export default {
  data() {
    return {
      count: 0,
      name: 'Vue'
    }
  },
  computed: {
    doubledCount() {
      return this.count * 2;
    }
  },
  methods: {
    increment() {
      this.count++;
    }
  },
  mounted() {
    console.log('组件已挂载');
  }
}

而在 Vue 3 中,我们可以使用 Composition API 来实现相同的功能:

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

export default {
  setup() {
    const count = ref(0);
    const name = ref('Vue');
    
    const doubledCount = computed(() => count.value * 2);
    
    const increment = () => {
      count.value++;
    };
    
    onMounted(() => {
      console.log('组件已挂载');
    });
    
    return {
      count,
      name,
      doubledCount,
      increment
    };
  }
}

响应式数据处理

ref 和 reactive 的基本使用

ref 的使用

ref 是 Vue 3 中最基本的响应式数据包装器,它可以为任意类型的数据创建响应式引用:

import { ref } from 'vue';

// 创建基础类型的响应式数据
const count = ref(0);
const message = ref('Hello Vue');

// 访问和修改值
console.log(count.value); // 0
count.value = 10;
console.log(count.value); // 10

// 在模板中使用
// <template>
//   <p>{{ count }}</p>
// </template>

reactive 的使用

reactive 用于创建响应式对象,它会将对象的所有属性都转换为响应式:

import { reactive } from 'vue';

const state = reactive({
  count: 0,
  name: 'Vue',
  user: {
    age: 25,
    email: 'vue@example.com'
  }
});

// 修改属性
state.count = 10;
state.user.age = 30;

// 在模板中使用
// <template>
//   <p>{{ state.count }}</p>
//   <p>{{ state.user.age }}</p>
// </template>

深层响应式对象处理

对于嵌套的对象,Vue 3 会自动将其转换为响应式:

import { reactive } from 'vue';

const user = reactive({
  profile: {
    personal: {
      name: 'John',
      age: 25
    }
  }
});

// 这样修改是响应式的
user.profile.personal.name = 'Jane';
// 或者
user.profile = {
  ...user.profile,
  personal: {
    ...user.profile.personal,
    name: 'Jane'
  }
};

readonly 和 shallowReactive

readonly

readonly 可以创建一个只读的响应式对象:

import { reactive, readonly } from 'vue';

const original = reactive({ count: 0 });
const copy = readonly(original);

// 修改会触发警告,但不会生效
copy.count = 1; // 警告:不能修改只读属性

shallowReactive

shallowReactive 只将对象顶层属性转换为响应式:

import { shallowReactive } from 'vue';

const state = shallowReactive({
  nested: { count: 0 },
  array: [1, 2, 3]
});

// 这样修改是响应式的
state.nested.count = 1; // 不会触发响应式更新
state.array.push(4); // 会触发响应式更新

组合函数复用

创建组合函数

组合函数是 Composition API 的核心概念之一,它允许我们将可复用的逻辑封装成函数:

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

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

使用组合函数

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

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

更复杂的组合函数示例

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

export function useFetch(url) {
  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);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      data.value = await response.json();
    } catch (err) {
      error.value = err.message;
    } finally {
      loading.value = false;
    }
  };
  
  // 立即执行
  fetchData();
  
  // 监听 url 变化,重新获取数据
  watch(url, fetchData);
  
  return {
    data,
    loading,
    error,
    refetch: fetchData
  };
}

生命周期钩子

基本生命周期钩子

Composition API 提供了与 Vue 2 相同的生命周期钩子,但以函数的形式提供:

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

export default {
  setup() {
    onBeforeMount(() => {
      console.log('组件即将挂载');
    });
    
    onMounted(() => {
      console.log('组件已挂载');
    });
    
    onBeforeUpdate(() => {
      console.log('组件即将更新');
    });
    
    onUpdated(() => {
      console.log('组件已更新');
    });
    
    onBeforeUnmount(() => {
      console.log('组件即将卸载');
    });
    
    onUnmounted(() => {
      console.log('组件已卸载');
    });
    
    return {};
  }
};

生命周期钩子的最佳实践

// 带有副作用清理的生命周期钩子
import { ref, onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const timer = ref(null);
    const count = ref(0);
    
    // 设置定时器
    onMounted(() => {
      timer.value = setInterval(() => {
        count.value++;
      }, 1000);
    });
    
    // 清理定时器
    onUnmounted(() => {
      if (timer.value) {
        clearInterval(timer.value);
      }
    });
    
    return { count };
  }
};

计算属性和侦听器

computed 计算属性

import { ref, computed } from 'vue';

export default {
  setup() {
    const firstName = ref('John');
    const lastName = ref('Doe');
    
    // 基本计算属性
    const fullName = computed(() => {
      return `${firstName.value} ${lastName.value}`;
    });
    
    // 带 getter 和 setter 的计算属性
    const fullNameWithSetter = computed({
      get: () => `${firstName.value} ${lastName.value}`,
      set: (value) => {
        const names = value.split(' ');
        firstName.value = names[0];
        lastName.value = names[1];
      }
    });
    
    return {
      firstName,
      lastName,
      fullName,
      fullNameWithSetter
    };
  }
};

watch 侦听器

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

export default {
  setup() {
    const count = ref(0);
    const name = ref('Vue');
    
    // 基本侦听器
    watch(count, (newVal, oldVal) => {
      console.log(`count 从 ${oldVal} 变为 ${newVal}`);
    });
    
    // 侦听多个源
    watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
      console.log(`count: ${oldCount} -> ${newCount}, name: ${oldName} -> ${newName}`);
    });
    
    // 深度侦听
    const user = ref({
      profile: {
        name: 'John'
      }
    });
    
    watch(user, (newVal, oldVal) => {
      console.log('用户信息发生变化');
    }, { deep: true });
    
    // 立即执行的侦听器
    watch(count, (newVal) => {
      console.log(`count 变为 ${newVal}`);
    }, { immediate: true });
    
    // watchEffect
    const effect = watchEffect(() => {
      console.log(`当前 count 值: ${count.value}`);
      // 当 count 改变时,这个函数会重新执行
    });
    
    // 清理副作用
    const cleanup = watchEffect((onInvalidate) => {
      const timer = setTimeout(() => {
        console.log('定时器执行');
      }, 1000);
      
      onInvalidate(() => {
        clearTimeout(timer);
        console.log('清理定时器');
      });
    });
    
    return { count, name };
  }
};

组件间通信

Props 和 emits

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

export default {
  props: {
    title: {
      type: String,
      required: true
    },
    count: {
      type: Number,
      default: 0
    }
  },
  
  emits: ['update-count', 'submit'],
  
  setup(props, { emit }) {
    const localCount = ref(props.count);
    
    const handleIncrement = () => {
      localCount.value++;
      emit('update-count', localCount.value);
    };
    
    const handleSubmit = () => {
      emit('submit', { count: localCount.value });
    };
    
    return {
      localCount,
      handleIncrement,
      handleSubmit
    };
  }
};
// 父组件
import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const title = ref('我的组件');
    
    const handleUpdateCount = (newCount) => {
      count.value = newCount;
    };
    
    const handleSubmit = (data) => {
      console.log('提交数据:', data);
    };
    
    return {
      count,
      title,
      handleUpdateCount,
      handleSubmit
    };
  }
};

provide 和 inject

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

export default {
  setup() {
    const theme = ref('dark');
    const user = ref({ name: 'John', role: 'admin' });
    
    provide('theme', theme);
    provide('user', user);
    provide('updateTheme', (newTheme) => {
      theme.value = newTheme;
    });
    
    return { theme, user };
  }
};
// 子组件
import { inject } from 'vue';

export default {
  setup() {
    const theme = inject('theme');
    const user = inject('user');
    const updateTheme = inject('updateTheme');
    
    const toggleTheme = () => {
      updateTheme(theme.value === 'dark' ? 'light' : 'dark');
    };
    
    return {
      theme,
      user,
      toggleTheme
    };
  }
};

状态管理模式

基础状态管理

// stores/useGlobalStore.js
import { reactive } from 'vue';

export function useGlobalStore() {
  const state = reactive({
    user: null,
    isLoggedIn: false,
    theme: 'light'
  });
  
  const setUser = (user) => {
    state.user = user;
    state.isLoggedIn = !!user;
  };
  
  const setTheme = (theme) => {
    state.theme = theme;
  };
  
  const logout = () => {
    state.user = null;
    state.isLoggedIn = false;
  };
  
  return {
    state,
    setUser,
    setTheme,
    logout
  };
}

使用状态管理

// App.vue
import { useGlobalStore } from '@/stores/useGlobalStore';
import { onMounted } from 'vue';

export default {
  setup() {
    const { state, setUser, setTheme, logout } = useGlobalStore();
    
    onMounted(() => {
      // 模拟从 API 获取用户信息
      setTimeout(() => {
        setUser({
          id: 1,
          name: 'John Doe',
          email: 'john@example.com'
        });
      }, 1000);
    });
    
    return {
      state,
      logout,
      setTheme
    };
  }
};

复杂状态管理示例

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

export function useTodoStore() {
  const todos = ref([]);
  const filter = ref('all');
  
  // 添加待办事项
  const addTodo = (text) => {
    const newTodo = {
      id: Date.now(),
      text,
      completed: false,
      createdAt: new Date()
    };
    todos.value.push(newTodo);
  };
  
  // 切换完成状态
  const toggleTodo = (id) => {
    const todo = todos.value.find(t => t.id === id);
    if (todo) {
      todo.completed = !todo.completed;
    }
  };
  
  // 删除待办事项
  const deleteTodo = (id) => {
    todos.value = todos.value.filter(t => t.id !== id);
  };
  
  // 清除已完成的待办事项
  const clearCompleted = () => {
    todos.value = todos.value.filter(t => !t.completed);
  };
  
  // 计算过滤后的待办事项
  const filteredTodos = computed(() => {
    switch (filter.value) {
      case 'active':
        return todos.value.filter(todo => !todo.completed);
      case 'completed':
        return todos.value.filter(todo => todo.completed);
      default:
        return todos.value;
    }
  });
  
  // 计算未完成的待办事项数量
  const activeCount = computed(() => {
    return todos.value.filter(todo => !todo.completed).length;
  });
  
  // 计算已完成的待办事项数量
  const completedCount = computed(() => {
    return todos.value.filter(todo => todo.completed).length;
  });
  
  // 设置过滤器
  const setFilter = (newFilter) => {
    filter.value = newFilter;
  };
  
  return {
    todos,
    filter,
    filteredTodos,
    activeCount,
    completedCount,
    addTodo,
    toggleTodo,
    deleteTodo,
    clearCompleted,
    setFilter
  };
}

高级技巧和最佳实践

响应式数据的性能优化

// 使用 computed 缓存复杂计算
import { ref, computed } from 'vue';

export default {
  setup() {
    const items = ref([]);
    const searchTerm = ref('');
    
    // 复杂的过滤计算,使用 computed 缓存
    const filteredItems = computed(() => {
      if (!searchTerm.value) return items.value;
      
      return items.value.filter(item => 
        item.name.toLowerCase().includes(searchTerm.value.toLowerCase())
      );
    });
    
    // 对于非常复杂的计算,可以使用缓存策略
    const expensiveCalculation = computed({
      get: () => {
        // 复杂的计算逻辑
        return items.value.reduce((sum, item) => sum + item.value, 0);
      },
      set: (value) => {
        // 可以设置 setter 来更新基础数据
        console.log('计算结果:', value);
      }
    });
    
    return {
      items,
      searchTerm,
      filteredItems,
      expensiveCalculation
    };
  }
};

组合函数的测试

// composables/__tests__/useCounter.spec.js
import { useCounter } from '../useCounter';
import { nextTick } from 'vue';

describe('useCounter', () => {
  it('should initialize with correct value', () => {
    const { count } = useCounter(5);
    expect(count.value).toBe(5);
  });
  
  it('should increment correctly', () => {
    const { count, increment } = useCounter();
    increment();
    expect(count.value).toBe(1);
  });
  
  it('should decrement correctly', () => {
    const { count, decrement } = useCounter(10);
    decrement();
    expect(count.value).toBe(9);
  });
  
  it('should reset correctly', () => {
    const { count, reset } = useCounter(5);
    count.value = 10;
    reset();
    expect(count.value).toBe(5);
  });
});

错误处理和边界情况

// composables/useAsyncData.js
import { ref, watch } from 'vue';

export function useAsyncData(fetcher, options = {}) {
  const data = ref(null);
  const loading = ref(false);
  const error = ref(null);
  const retryCount = ref(0);
  
  const fetchData = async (params = {}) => {
    if (loading.value) return;
    
    loading.value = true;
    error.value = null;
    
    try {
      const result = await fetcher(params);
      data.value = result;
      retryCount.value = 0;
    } catch (err) {
      error.value = err;
      console.error('数据获取失败:', err);
      
      // 重试逻辑
      if (options.retry && retryCount.value < options.retry.maxAttempts) {
        retryCount.value++;
        setTimeout(() => fetchData(params), options.retry.delay || 1000);
      }
    } finally {
      loading.value = false;
    }
  };
  
  // 自动重新获取数据
  if (options.autoFetch !== false) {
    fetchData();
  }
  
  const refresh = () => fetchData();
  
  return {
    data,
    loading,
    error,
    retryCount,
    refresh,
    fetchData
  };
}

TypeScript 支持

// composables/useTypedCounter.ts
import { ref, computed } from 'vue';

interface CounterState {
  count: number;
  name: string;
}

export function useTypedCounter(initialValue: number = 0) {
  const count = ref<number>(initialValue);
  const name = ref<string>('Vue');
  
  const doubled = computed<number>(() => count.value * 2);
  
  const increment = () => {
    count.value++;
  };
  
  const decrement = () => {
    count.value--;
  };
  
  const reset = (value: number = initialValue) => {
    count.value = value;
  };
  
  return {
    count,
    name,
    doubled,
    increment,
    decrement,
    reset
  };
}

总结

Vue 3 的 Composition API 为前端开发带来了前所未有的灵活性和强大功能。通过本文的详细介绍,我们了解了:

  1. 响应式数据处理:掌握了 ref 和 reactive 的基本使用以及高级特性
  2. 组合函数复用:学会了如何创建可复用的逻辑模块
  3. 生命周期管理:理解了如何在 Composition API 中使用生命周期钩子
  4. 计算属性和侦听器:掌握了复杂的响应式编程技巧
  5. 组件间通信:了解了 props、emits、provide 和 inject 的使用
  6. 状态管理模式:学习了如何构建复杂的状态管理解决方案

Composition API 不仅让代码更加模块化和可复用,还提供了更好的类型支持和开发体验。随着 Vue 3 的普及,掌握 Composition API 已经成为现代前端开发者必备的技能。

通过实践这些最佳实践,我们可以构建出更加健壮、可维护的 Vue 应用程序。记住,在使用 Composition API 时要保持代码的简洁性,合理组织逻辑,充分利用组合函数来提高代码复用率。

相关推荐
广告位招租

相似文章

    评论 (0)

    0/2000