Vue 3.4 Composition API最佳实践:从基础到高级技巧

Vue 3.4 Composition API最佳实践:从基础到高级技巧

Vue 3的Composition API彻底改变了Vue应用的开发方式,提供了更灵活、更强大的代码组织能力。随着Vue 3.4的发布,Composition API变得更加成熟和完善。本文将深入探讨Composition API的最佳实践,涵盖从基础使用到高级技巧的各个方面。

1. 基础模式:正确的响应式数据管理

正确使用响应式API是Composition API的基础。理解不同API的适用场景至关重要。

import { ref, reactive, computed, watch, watchEffect } from "vue";

// 1. ref - 适用于基本类型和对象引用
const count = ref(0);
const user = ref({ name: "Alice", age: 25 });

// 2. reactive - 适用于复杂对象
const state = reactive({
  items: [],
  loading: false,
  error: null
});

// 3. computed - 计算属性
const totalItems = computed(() => state.items.length);
const filteredItems = computed(() => {
  return state.items.filter(item => item.active);
});

// 4. watch - 精确监听
watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`);
}, { immediate: true });

// 5. watchEffect - 自动依赖追踪
watchEffect(() => {
  console.log(`Total items: ${totalItems.value}`);
  if (totalItems.value > 10) {
    console.log("Too many items!");
  }
});

// 最佳实践:统一响应式数据源
const useUserStore = () => {
  const user = ref(null);
  const isAuthenticated = computed(() => !!user.value);
  
  const login = async (credentials) => {
    try {
      const response = await api.login(credentials);
      user.value = response.data;
      localStorage.setItem("token", response.token);
    } catch (error) {
      console.error("Login failed:", error);
      throw error;
    }
  };
  
  const logout = () => {
    user.value = null;
    localStorage.removeItem("token");
  };
  
  return {
    user: readonly(user),
    isAuthenticated,
    login,
    logout
  };
};

2. 组合式函数:可复用的逻辑封装

组合式函数是Composition API的核心,让逻辑复用变得简单直观。

// useFetch - 通用的数据获取hook
import { ref, watchEffect } from "vue";

export function useFetch(url, options = {}) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(false);
  
  const fetchData = async () => {
    loading.value = true;
    error.value = null;
    
    try {
      const response = await fetch(url, options);
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      data.value = await response.json();
    } catch (err) {
      error.value = err;
      data.value = null;
    } finally {
      loading.value = false;
    }
  };
  
  // 自动重新获取(当url变化时)
  watchEffect(() => {
    if (url) {
      fetchData();
    }
  });
  
  return {
    data,
    error,
    loading,
    refresh: fetchData
  };
}

// useLocalStorage - 本地存储hook
import { ref, watch } from "vue";

export function useLocalStorage(key, defaultValue) {
  const data = ref(defaultValue);
  
  // 从localStorage读取初始值
  try {
    const item = localStorage.getItem(key);
    if (item) {
      data.value = JSON.parse(item);
    }
  } catch (error) {
    console.error(`Error reading localStorage key "${key}":`, error);
  }
  
  // 监听变化并保存到localStorage
  watch(data, (newValue) => {
    try {
      localStorage.setItem(key, JSON.stringify(newValue));
    } catch (error) {
      console.error(`Error saving to localStorage key "${key}":`, error);
    }
  }, { deep: true });
  
  return data;
}

// useDebounce - 防抖hook
import { ref, watch } from "vue";

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

// 在组件中使用
import { useFetch, useLocalStorage, useDebounce } from "./composables";

const Component = {
  setup() {
    const searchQuery = ref("");
    const debouncedQuery = useDebounce(searchQuery, 500);
    
    const { data: users, loading, error } = useFetch(
      `https://api.example.com/users?q=${debouncedQuery.value}`
    );
    
    const theme = useLocalStorage("theme", "light");
    
    return {
      searchQuery,
      users,
      loading,
      error,
      theme
    };
  }
};

3. TypeScript集成:类型安全的Composition API

Vue 3.4对TypeScript的支持更加完善,充分利用类型系统可以大幅提升代码质量。

import { ref, computed, Ref, ComputedRef } from "vue";

// 定义类型
interface User {
  id: number;
  name: string;
  email: string;
  role: "admin" | "user" | "guest";
}

interface Pagination {
  page: number;
  limit: number;
  total: number;
}

// 类型安全的组合式函数
function useUserManagement() {
  const users: Ref = ref([]);
  const selectedUser: Ref = ref(null);
  const loading: Ref = ref(false);
  
  const pagination: Ref = ref({
    page: 1,
    limit: 20,
    total: 0
  });
  
  // 计算属性类型
  const totalPages: ComputedRef = computed(() => {
    return Math.ceil(pagination.value.total / pagination.value.limit);
  });
  
  const hasNextPage: ComputedRef = computed(() => {
    return pagination.value.page < totalPages.value;
  });
  
  // 异步函数类型
  async function fetchUsers(): Promise {
    loading.value = true;
    try {
      const response = await api.getUsers({
        page: pagination.value.page,
        limit: pagination.value.limit
      });
      
      users.value = response.data;
      pagination.value.total = response.total;
    } catch (error) {
      console.error("Failed to fetch users:", error);
      throw error;
    } finally {
      loading.value = false;
    }
  }
  
  function selectUser(user: User): void {
    selectedUser.value = user;
  }
  
  function updateUser(id: number, updates: Partial): void {
    const index = users.value.findIndex(user => user.id === id);
    if (index !== -1) {
      users.value[index] = { ...users.value[index], ...updates };
    }
  }
  
  return {
    users: users as Readonly>>,
    selectedUser,
    loading,
    pagination,
    totalPages,
    hasNextPage,
    fetchUsers,
    selectUser,
    updateUser
  };
}

// 泛型组合式函数
function useResource(fetchFn: () => Promise) {
  const data: Ref = ref(null);
  const loading = ref(false);
  const error = ref(null);
  
  async function load() {
    loading.value = true;
    error.value = null;
    
    try {
      data.value = await fetchFn();
    } catch (err) {
      error.value = err as Error;
      data.value = null;
    } finally {
      loading.value = false;
    }
  }
  
  return {
    data,
    loading,
    error,
    load
  };
}

// 使用示例
const { data: post, load: loadPost } = useResource(() => 
  api.getPost(1)
);

4. 性能优化:避免常见的性能陷阱

Composition API虽然强大,但也需要注意性能优化。

// 1. 避免不必要的响应式
import { ref, shallowRef, markRaw } from "vue";

// 大型对象使用shallowRef
const largeData = shallowRef({ /* 大量数据 */ });

// 静态配置使用markRaw
const config = markRaw({
  apiUrl: "https://api.example.com",
  timeout: 5000,
  retries: 3
});

// 2. 合理使用watch和watchEffect
const searchQuery = ref("");
const selectedCategory = ref("all");

// 不好的做法:多个独立的watch
watch(searchQuery, fetchResults);
watch(selectedCategory, fetchResults);

// 好的做法:合并监听
watch([searchQuery, selectedCategory], ([query, category]) => {
  fetchResults(query, category);
}, { immediate: true });

// 3. 使用computed缓存计算结果
const expensiveCalculation = computed(() => {
  // 复杂的计算逻辑
  return heavyData.value.reduce((sum, item) => {
    return sum + processItem(item);
  }, 0);
});

// 4. 懒加载组合式函数
const useHeavyFeature = () => {
  // 只有在需要时才导入
  const heavyModule = import("./heavy-module");
  
  return {
    // ...
  };
};

// 5. 使用provide/inject进行状态共享
// 父组件
import { provide, reactive } from "vue";

const App = {
  setup() {
    const appState = reactive({
      theme: "light",
      language: "zh-CN",
      user: null
    });
    
    provide("appState", appState);
    
    return { appState };
  }
};

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

const ChildComponent = {
  setup() {
    const appState = inject("appState");
    
    return { appState };
  }
};

5. 测试组合式函数

组合式函数的可测试性是其重要优势之一。

// useCounter.js
export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  
  const increment = () => {
    count.value++;
  };
  
  const decrement = () => {
    count.value--;
  };
  
  const reset = () => {
    count.value = initialValue;
  };
  
  return {
    count: readonly(count),
    increment,
    decrement,
    reset
  };
}

// useCounter.test.js
import { describe, it, expect } from "vitest";
import { ref } from "vue";
import { useCounter } from "./useCounter";

describe("useCounter", () => {
  it("should initialize with default value", () => {
    const { count } = useCounter();
    expect(count.value).toBe(0);
  });
  
  it("should initialize with custom value", () => {
    const { count } = useCounter(10);
    expect(count.value).toBe(10);
  });
  
  it("should increment the count", () => {
    const { count, increment } = useCounter(5);
    increment();
    expect(count.value).toBe(6);
  });
  
  it("should decrement the count", () => {
    const { count, decrement } = useCounter(5);
    decrement();
    expect(count.value).toBe(4);
  });
  
  it("should reset to initial value", () => {
    const { count, increment, reset } = useCounter(5);
    increment();
    increment();
    expect(count.value).toBe(7);
    reset();
    expect(count.value).toBe(5);
  });
  
  it("should not allow direct mutation", () => {
    const { count } = useCounter(5);
    // count.value = 10; // TypeScript会报错,JavaScript会静默失败
    expect(count.value).toBe(5);
  });
});

总结

Vue 3.4的Composition API为Vue应用开发带来了革命性的改进:

  1. 更好的代码组织:逻辑关注点分离,代码更易维护
  2. 更强的复用性:组合式函数让逻辑复用变得简单
  3. 更完善的TypeScript支持:类型安全大幅提升开发体验
  4. 更灵活的响应式系统:细粒度的响应式控制
  5. 更好的可测试性:组合式函数易于单元测试

掌握Composition API的最佳实践,可以帮助开发者构建更健壮、更可维护的Vue应用。建议从基础模式开始,逐步掌握高级技巧,并在实际项目中不断实践和优化。

随着Vue生态的不断发展,Composition API将继续演进,为开发者提供更强大的工具。保持学习的态度,关注官方文档和社区动态,是提升Vue开发技能的关键。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容