返回首页

Vue3.0 的 Composition API 与 Vue2.x 的 Options API 有什么不同?

问题解析

这是 Vue3 最核心的变化之一。面试考察这个问题,是想要了解候选人对 Vue3 新特性的理解,以及是否能够掌握更高效的代码组织方式。

核心概念

什么是 Options API

Options API(选项式 API)是 Vue2 的传统写法,通过 datamethodscomputedwatch 等选项来组织代码:

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

什么是 Composition API

Composition API(组合式 API)是 Vue3 引入的新方式,通过 setup 函数和相关 API 来组织代码:

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

export default {
  setup() {
    const count = ref(0);
    const message = ref('');

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

    const increment = () => {
      count.value++;
    };

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

    return {
      count,
      message,
      doubleCount,
      increment
    };
  }
}

详细解答

1. 逻辑组织方式对比

Options API 的问题:

┌─────────────────────────────────────────┐
│  大型组件中的代码分布                      │
├─────────────────────────────────────────┤
│  data: [属性A, 属性B, 属性C...]          │
│  methods: [方法A, 方法B, 方法C...]       │
│  computed: [计算A, 计算B, 计算C...]      │
│  watch: [监听A, 监听B, 监听C...]         │
└─────────────────────────────────────────┘
         ↓ 按选项类型分散
问题:一个功能的代码分散在不同选项中

Composition API 的优势:

┌─────────────────────────────────────────┐
│  功能A: [data + methods + computed]      │
│  功能B: [data + methods + computed]      │
│  功能C: [data + methods + computed]      │
└─────────────────────────────────────────┘
         ↓ 按功能逻辑聚合
优势:高内聚、低耦合

2. 代码复用方式对比

Vue2 - Mixin 的问题:

// mouseMixin.js
export const mouseMixin = {
  data() {
    return { x: 0, y: 0 };
  },
  mounted() {
    window.addEventListener('mousemove', this.update);
  },
  methods: {
    update(e) {
      this.x = e.pageX;
      this.y = e.pageY;
    }
  }
};

// 组件中使用
export default {
  mixins: [mouseMixin, otherMixin, anotherMixin],
  // 问题1: 命名冲突(多个 mixin 可能有相同的属性名)
  // 问题2: 数据来源不清晰(这个 x 来自哪个 mixin?)
}

Vue3 - Composable 函数:

// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue';

export function useMouse() {
  const x = ref(0);
  const y = ref(0);

  const update = (e) => {
    x.value = e.pageX;
    y.value = e.pageY;
  };

  onMounted(() => window.addEventListener('mousemove', update));
  onUnmounted(() => window.removeEventListener('mousemove', update));

  return { x, y };
}

// 组件中使用
import { useMouse } from './useMouse';
import { useOther } from './useOther';

export default {
  setup() {
    const { x, y } = useMouse();  // 来源清晰
    const { data } = useOther();   // 来源清晰

    return { x, y, data };
  }
}

3. TypeScript 支持

Options API 的类型问题:

export default {
  data() {
    return {
      count: 0  // 类型推断有限
    };
  },
  methods: {
    increment() {
      this.count++;  // this 的类型推断不完整
    }
  }
}

Composition API 的类型优势:

import { ref, computed } from 'vue';

export default {
  setup() {
    const count = ref<number>(0);  // 完整的类型推断
    const double = computed<number>(() => count.value * 2);

    const increment = (): void => {
      count.value++;
    };

    return { count, double, increment };
  }
}

深入理解

1. 为什么 Composition API 更易维护

// 实际案例:一个包含搜索、分页、排序的表格组件

// Options API 写法 - 逻辑分散
export default {
  data() {
    return {
      searchQuery: '',    // 搜索相关
      searchResults: [],
      currentPage: 1,     // 分页相关
      pageSize: 10,
      total: 0,
      sortField: '',      // 排序相关
      sortOrder: 'asc'
    };
  },
  methods: {
    handleSearch() { /* ... */ },      // 搜索方法
    handlePageChange() { /* ... */ }, // 分页方法
    handleSort() { /* ... */ }        // 排序方法
  }
  // 修改搜索功能需要在 data、methods、computed 之间跳转
}

// Composition API 写法 - 逻辑聚合
export default {
  setup() {
    // 搜索功能内聚
    const { searchQuery, searchResults, handleSearch } = useSearch();

    // 分页功能内聚
    const { currentPage, pageSize, total, handlePageChange } = usePagination();

    // 排序功能内聚
    const { sortField, sortOrder, handleSort } = useSorting();

    return {
      searchQuery, searchResults, handleSearch,
      currentPage, pageSize, total, handlePageChange,
      sortField, sortOrder, handleSort
    };
  }
}

2. Setup 语法糖(更简洁的写法)

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

// 直接定义变量和方法
const count = ref(0);
const double = computed(() => count.value * 2);
const increment = () => count.value++;

// 无需 return,自动暴露给模板
</script>

3. 响应式系统的统一

// Vue2: 响应式数据定义在 data 中,methods 中的 this 指向组件实例
export default {
  data() {
    return { count: 0 };
  },
  methods: {
    increment() {
      this.count++;  // 依赖 this
    }
  }
}

// Vue3: 统一的响应式 API,不依赖 this
import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);
    const increment = () => count.value++;  // 不依赖 this
    return { count, increment };
  }
}

最佳实践

1. 何时使用 Options API

// 小型组件、简单逻辑时,Options API 仍然简洁
export default {
  data() {
    return {
      isShow: false,
      title: 'Hello'
    };
  },
  methods: {
    toggle() {
      this.isShow = !this.isShow;
    }
  }
}

2. Composable 函数的命名规范

// 使用 use 前缀
useMouse.js      // 鼠标位置
useFetch.js      // 数据请求
useLocalStorage.js  // 本地存储
useDebounce.js   // 防抖

// 清晰的功能命名
export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  const increment = () => count.value++;
  const decrement = () => count.value--;

  return {
    count,
    increment,
    decrement
  };
}

3. 代码组织原则

export default {
  setup() {
    // 1. 响应式数据定义
    const state = reactive({
      list: [],
      loading: false
    });

    // 2. 计算属性
    const total = computed(() => state.list.length);

    // 3. 方法定义
    const fetchData = async () => {
      state.loading = true;
      // ...
      state.loading = false;
    };

    // 4. 生命周期钩子
    onMounted(fetchData);

    // 5. 返回给模板使用
    return {
      ...toRefs(state),
      total,
      fetchData
    };
  }
}

4. 迁移策略

// Vue3 中仍然支持 Options API
// 可以渐进式迁移:
// 1. 新功能使用 Composition API
// 2. 旧组件保持 Options API
// 3. 逐步重构核心组件

// 混合使用(不推荐长期使用)
export default {
  data() {
    return { legacyData: 'old' };
  },
  setup() {
    const newData = ref('new');
    return { newData };
  }
}

面试要点

  1. 能够清晰对比两种 API 的组织方式差异

    • Options API:按选项类型组织
    • Composition API:按功能逻辑组织
  2. 理解 Mixin 的缺陷和 Composable 的优势

    • 命名冲突、数据来源不清晰 vs 来源清晰、可组合
  3. 知道 Composition API 更适合的场景

    • 大型组件、复杂逻辑、需要复用逻辑、TypeScript 项目
  4. 了解 <script setup> 语法糖

    • 更简洁的写法,自动暴露变量
  5. 理解响应式 API(ref/reactive)

    • ref 用于基本类型和需要替换的对象
    • reactive 用于对象类型

核心结论

  • Composition API 解决了 Options API 在大型组件中的逻辑分散问题
  • 提供了更好的代码复用机制(Composable)
  • 拥有更好的 TypeScript 类型推断
  • 不意味着完全抛弃 Options API,小型组件仍可继续使用