跳到正文
前端知识库
前端框架

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

面试考察这个问题,是想了解不同 API 的逻辑组织、复用与类型推断。Options API 仍受 Vue3 支持;Vue 2.7 也内置了 Composition API,不能把两种写法直接等同于两个大版本的全部区别。

约 5 分钟

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

问题解析

面试考察这个问题,是想了解不同 API 的逻辑组织、复用与类型推断。Options API 仍受 Vue3 支持;Vue 2.7 也内置了 Composition API,不能把两种写法直接等同于两个大版本的全部区别。

核心概念

什么是 Options API

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

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 的类型边界:

裸对象缺少组件类型上下文时,跨选项的 this 推断较困难。Vue3 的 defineComponent 能为 Options API 提供推断,不应概括为“Options API 不支持 TypeScript”。Composition API 使用普通变量和函数参数,尤其便于组合函数独立推断、导出和测试;ref(0) 本身即可推断数值类型,无须总写显式泛型。

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 };
  }
}

最佳实践

0. 组合函数不会自动带来合理组织

Vue2 已能通过普通函数、mixin、组件与作用域插槽复用逻辑;Composition API 的优势在于依赖、返回值和调用关系更显式,而不是第一次提供复用能力。一个巨大的 setup 同样难以维护,应按业务职责拆分并明确输入输出。

  • 从 reactive 对象直接解构原始值会失去与原属性的联动;需要联动时保留对象访问,或使用 toRef/toRefs。
  • composable 返回独立 ref 时可以解构,但调用方仍需理解 .value 与模板解包规则。
  • 生命周期依赖当前组件实例;监听器、计时器和请求要明确清理责任,异步创建的 watcher 不能一概假定会随组件自动停止。
  • 默认把可变状态创建在函数内部;模块级状态意味着多个调用方共享,也要考虑 SSR 请求隔离。

这些约束与组织方式、响应式机制分别讨论,不能把“换成组合式 API”当作性能优化或迁移成功的证据。

onRenderTracked 用于观察组件渲染时收集了哪些响应式依赖,onRenderTriggered 用于观察哪个依赖变化触发了渲染 effect;Options API 对应 renderTracked / renderTriggered。这些是开发环境调试钩子,不在生产构建或 SSR 渲染中作为常规监控事件使用。可借助事件的 target/key/type 定位意外订阅,并在钩子内断点;不要在钩子中继续修改所观察的响应式状态,以免引入额外更新或循环。

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 更适合的场景(知识点:何时使用 Options API)

    • 大型组件、复杂逻辑、需要复用逻辑、TypeScript 项目
  4. 了解 <script setup> 语法糖(知识点:Setup 语法糖)

    • 更简洁的写法,自动暴露变量
  5. 理解响应式 API(ref/reactive)(知识点:响应式系统的统一)

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

核心结论:

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