Why does TypeScript inference work more naturally with Vue 3's Composition API than with the Options API?
answer
- plain variables versus a merged this
- defineComponent wrapper
- type gymnastics for the instance
- mixins and injection break down
- annotations for circular computed
basics
~20 sComposition API code is plain variables and functions whose types TypeScript infers directly; the Options API needs defineComponent and complex types to rebuild this from several options, and inference still breaks down for mixins and injection.
solid answer
~40 sIn the Composition API, `const count = ref(0)` is a `Ref<number>` and a composable's return type is inferred like any function's, so most code needs no annotations and looks almost identical in TypeScript and JavaScript. The Options API was designed in 2013 without type inference in mind. To type `this` inside `computed`, `methods` and hooks, TypeScript has to reconstruct the instance from `props`, `data`, `computed`, `methods` and more, which Vue does with heavy generic types behind `defineComponent()`. Without that wrapper, props are not inferred. Even with it, the docs say inference can break down for mixins and dependency injection, and some computed properties need explicit return types to escape circular inference.
code
ts · 15 linesimport { defineComponent, type PropType } from 'vue'
interface Book { title: string; year: number }
export default defineComponent({ // required for props inference on this
props: { book: { type: Object as PropType<Book>, required: true } },
data() {
return { copies: 1 }
},
computed: {
label(): string { // explicit return type: guards against circular inference
return `${this.book.title} (${this.copies})`
}
}
})go deeper
Recall that Composition API code is plain variables and functions, which TypeScript infers directly, while the Options API needs defineComponent.
Explain why typing this means merging types from many options, and name the weak spots: mixins, injection, circular computed properties.
Use inference as a concrete argument in style decisions, and know which Options API components are cheap to keep typed and which are not.
Weigh TypeScript adoption and API style together: the cost of keeping options code typed against converting its most complex components.
## Where inference comes from TypeScript infers types from values and from function signatures. Code made of **local variables and function calls** is the easiest case: every expression has a type the compiler can follow without help. Code that reaches state through a **dynamic receiver**, such as `this` built by merging several objects, is the hardest, because the type of `this` has to be computed from all of those objects at once. The two Vue styles sit at opposite ends of that spectrum. ## Composition API: ordinary TypeScript - `ref(0)` returns `Ref<number>`; `computed(() => count.value * 2)` returns a `ComputedRef<number>`. - A composable is a function, so its return type is inferred and every destructured value is typed at the call site. - Functions that act on state are plain functions with ordinary parameter types. - In `<script setup>`, props and emits are declared with type-level macros and checked by the tooling. The docs sum it up: Composition API code enjoys full type inference with little need for manual type hints, and mostly looks the same in TypeScript and JavaScript. Plain JavaScript users get partial inference in editors as a side effect. ## Options API: rebuilding this In the Options API, `this` inside `computed`, `methods`, `watch` and hooks must expose props, `data()` results, computed values, methods, injected values and global properties. For TypeScript to know that, Vue's type definitions have to merge the types of each option into one instance type. The docs describe the effort as "absurdly complex type gymnastics", and several practical consequences follow: 1. **`defineComponent()` is required.** Type inference for props in the Options API needs the component wrapped in `defineComponent()`. Without it, `this` is effectively untyped. 2. **Complex prop types need `PropType`.** A runtime `type: Object` says nothing about the shape, so `type: Object as PropType<Book>` is used to describe it. 3. **Some computed properties need annotations.** Because computed values can refer to each other through `this`, TypeScript can hit circular inference; the docs recommend explicit return types in such cases. 4. **Mixins and injection are weak spots.** The docs note that inference can still break down for mixins and dependency injection, because their contribution to `this` comes from outside the component definition. ## Side by side | Concern | Options API | Composition API | |---|---|---| | How state is typed | through the merged `this` | from each variable's own type | | Wrapper needed | `defineComponent()` | none in `<script setup>` | | Reused logic | mixins, weakly inferred | composables, inferred as functions | | Computed values | may need return annotations | inferred from the getter | | Injected values | typed through the options machinery | typed at the `inject()` call | | Looks different from JavaScript | noticeably | barely | ## What this means for a decision Type inference is one of the three advantages the docs list for the Composition API, alongside logic reuse and code organisation. For a team that is committed to TypeScript, it is usually the most tangible one day to day: fewer annotations, no reliance on type machinery for `this`, and reusable logic that is typed without extra work. For a JavaScript-only team, the argument is weaker; the Options API with `defineComponent()` still gives useful editor hints. ## A note on what the Options API still offers The Options API is typed well enough for most components when wrapped in `defineComponent()`. The problems concentrate at the edges: large components with many interdependent computed values, mixins, and injected values. Those same edges are where the Composition API tends to be recommended anyway, so the inference argument and the organisation argument point the same way in large codebases.
- Why does inference in the Options API break down for mixins specifically?A mixin adds properties to `this` through option merging. For TypeScript to see them, the component's instance type has to include each mixin's contributed types, which the type machinery can only partly reconstruct. A composable, by contrast, is a function with an inferred return type, so what it adds is typed at the call site.
- Does a JavaScript-only team gain anything from this difference?Some. The docs note that Composition API code gets partial inference in editors even without TypeScript, because it is ordinary variables and functions. But the advantage is much larger for teams writing TypeScript, where it removes most annotations and the reliance on `defineComponent()` for `this`.
saying these in an interview costs you the question
- Options API components get full prop inference without defineComponent().
- The Composition API needs more manual type annotations than the Options API.
- Type inference differences disappear once a project enables strict mode.
- Mixins are fully typed in the Options API, so they are the typed way to reuse logic.
- TypeScript cannot be used with the Options API at all.