Vue 3 Composition API深度解析:从Options到Composition

Vue 3 Composition API深度解析:从Options到Composition

Vue 3的Composition API是Vue生态系统中最重大的变革之一。它彻底改变了我们组织和重用Vue组件逻辑的方式。本文将深入探讨Composition API的核心概念、优势以及实际应用。

1. Options API的局限性

在Vue 2中,我们使用Options API组织组件代码。虽然直观,但随着组件复杂度增加,会出现一些问题:

// Options API示例 - 逻辑分散
export default {
  data() {
    return {
      count: 0,
      searchQuery: "",
      filteredItems: []
    };
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    },
    searchResults() {
      return this.items.filter(item => 
        item.name.includes(this.searchQuery)
      );
    }
  },
  watch: {
    searchQuery(newVal) {
      this.fetchResults(newVal);
    }
  },
  methods: {
    increment() {
      this.count++;
    },
    fetchResults(query) {
      // 获取搜索结果
    }
  },
  mounted() {
    this.fetchResults(this.searchQuery);
  }
};

问题:相关逻辑分散在不同的选项中,难以维护和重用。

2. Composition API基础

Composition API通过setup()函数将相关逻辑组织在一起:

// Composition API基础示例
import { ref, computed, watch, onMounted } from "vue";

export default {
  setup() {
    // 计数器逻辑
    const count = ref(0);
    const doubleCount = computed(() => count.value * 2);
    const increment = () => {
      count.value++;
    };
    
    // 搜索逻辑
    const searchQuery = ref("");
    const items = ref([]);
    const searchResults = computed(() => {
      return items.value.filter(item => 
        item.name.includes(searchQuery.value)
      );
    });
    
    const fetchResults = async (query) => {
      const response = await fetch(`/api/search?q=${query}`);
      items.value = await response.json();
    };
    
    watch(searchQuery, (newVal) => {
      fetchResults(newVal);
    });
    
    onMounted(() => {
      fetchResults(searchQuery.value);
    });
    
    return {
      count,
      doubleCount,
      increment,
      searchQuery,
      searchResults
    };
  }
};

3. 自定义组合函数

Composition API的真正威力在于可重用的组合函数:

// 自定义组合函数:useCounter
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--;
  const reset = () => count.value = initialValue;
  
  return {
    count,
    doubleCount,
    increment,
    decrement,
    reset
  };
}

// 自定义组合函数:useSearch
import { ref, computed, watch } from "vue";

export function useSearch(initialQuery = "", fetchFn) {
  const searchQuery = ref(initialQuery);
  const results = ref([]);
  const isLoading = ref(false);
  const error = ref(null);
  
  const performSearch = async () => {
    if (!searchQuery.value.trim()) {
      results.value = [];
      return;
    }
    
    isLoading.value = true;
    error.value = null;
    
    try {
      results.value = await fetchFn(searchQuery.value);
    } catch (err) {
      error.value = err.message;
      results.value = [];
    } finally {
      isLoading.value = false;
    }
  };
  
  // 防抖搜索
  let timeoutId;
  watch(searchQuery, () => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(performSearch, 300);
  }, { immediate: true });
  
  return {
    searchQuery,
    results,
    isLoading,
    error,
    performSearch
  };
}

// 在组件中使用组合函数
import { useCounter, useSearch } from "./composables";

export default {
  setup() {
    const counter = useCounter(10);
    
    const search = useSearch("", async (query) => {
      const response = await fetch(`/api/products?q=${query}`);
      return response.json();
    });
    
    return {
      ...counter,
      ...search
    };
  }
};

4. 高级模式

4.1 依赖注入

// 提供依赖
import { provide, inject } from "vue";

// 创建上下文
const UserContext = Symbol("user");

export function provideUser(user) {
  provide(UserContext, user);
}

export function useUser() {
  const user = inject(UserContext);
  
  if (!user) {
    throw new Error("useUser必须在provideUser的组件内使用");
  }
  
  return user;
}

// 在父组件中
import { provideUser } from "./userContext";

export default {
  setup() {
    const user = reactive({
      id: 1,
      name: "张三",
      email: "zhangsan@example.com"
    });
    
    provideUser(user);
    
    return { user };
  }
};

// 在子组件中
import { useUser } from "./userContext";

export default {
  setup() {
    const user = useUser();
    
    return { user };
  }
};

4.2 生命周期钩子组合

// 组合生命周期逻辑
import { onMounted, onUnmounted, ref } from "vue";

export function useEventListener(target, event, callback) {
  onMounted(() => {
    target.addEventListener(event, callback);
  });
  
  onUnmounted(() => {
    target.removeEventListener(event, callback);
  });
}

export function useInterval(callback, delay) {
  let intervalId;
  
  onMounted(() => {
    intervalId = setInterval(callback, delay);
  });
  
  onUnmounted(() => {
    clearInterval(intervalId);
  });
}

// 在组件中使用
import { useEventListener, useInterval } from "./composables";

export default {
  setup() {
    const windowWidth = ref(window.innerWidth);
    
    // 监听窗口大小变化
    useEventListener(window, "resize", () => {
      windowWidth.value = window.innerWidth;
    });
    
    // 每秒更新一次时间
    const currentTime = ref(new Date());
    useInterval(() => {
      currentTime.value = new Date();
    }, 1000);
    
    return { windowWidth, currentTime };
  }
};

5. 最佳实践

  1. 单一职责:每个组合函数只做一件事
  2. 命名规范:使用use前缀命名组合函数
  3. 参数设计:提供合理的默认值和配置选项
  4. 返回值:返回响应式对象,保持一致性
  5. 错误处理:在组合函数中处理错误
  6. 类型安全:使用TypeScript获得更好的类型支持
// TypeScript示例
import { ref, Ref, computed, ComputedRef } from "vue";

interface UsePaginationOptions {
  items: T[];
  itemsPerPage?: number;
}

interface UsePaginationReturn {
  currentPage: Ref;
  totalPages: ComputedRef;
  paginatedItems: ComputedRef;
  nextPage: () => void;
  prevPage: () => void;
  goToPage: (page: number) => void;
}

export function usePagination(
  options: UsePaginationOptions
): UsePaginationReturn {
  const {
    items,
    itemsPerPage = 10
  } = options;
  
  const currentPage = ref(1);
  
  const totalPages = computed(() => 
    Math.ceil(items.length / itemsPerPage)
  );
  
  const paginatedItems = computed(() => {
    const start = (currentPage.value - 1) * itemsPerPage;
    const end = start + itemsPerPage;
    return items.slice(start, end);
  });
  
  const nextPage = () => {
    if (currentPage.value < totalPages.value) {
      currentPage.value++;
    }
  };
  
  const prevPage = () => {
    if (currentPage.value > 1) {
      currentPage.value--;
    }
  };
  
  const goToPage = (page: number) => {
    if (page >= 1 && page <= totalPages.value) {
      currentPage.value = page;
    }
  };
  
  return {
    currentPage,
    totalPages,
    paginatedItems,
    nextPage,
    prevPage,
    goToPage
  };
}

总结

Vue 3的Composition API不仅解决了Options API在复杂组件中的维护问题,更重要的是它提供了一种全新的逻辑组织和重用范式。通过自定义组合函数,我们可以:

  • 将相关逻辑组织在一起,提高代码可读性
  • 创建可重用的逻辑单元,减少代码重复
  • 更好地进行类型推导(配合TypeScript)
  • 实现更灵活的逻辑组合方式

对于新项目,强烈推荐使用Composition API。对于现有Vue 2项目,可以逐步迁移,先从新组件开始使用Composition API,然后逐步重构复杂组件。

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

请登录后发表评论

    暂无评论内容