docs icon indicating copy to clipboard operation
docs copied to clipboard

Suggestion: Clarify typing with computed<T> vs return annotation (excess property checks)

Open CesarWatrin opened this issue 3 months ago β€’ 0 comments

[docs] Clarify typing with computed<T> vs return annotation (excess property checks)

Summary

The docs recommend typing a computed value with a generic:

const double = computed<number>(() => {
  return count.value * 2
})

This works, but there is a subtle TypeScript behavior that can surprise users:

  • With computed<Foo>(...), TypeScript only checks that the return value is assignable to Foo.
  • Extra properties not present in Foo are silently allowed because excess property checks do not apply when inferring into a generic.
  • Using a function return type annotation (): Foo => ... does enforce excess property checks and will reject extra properties.

Minimal reproduction

import { computed } from 'vue'

interface FooBarInterface {
  foo: boolean
}

// 1) Generic parameter (loose): allows extra props
const first = computed<FooBarInterface>(() => ({
  foo: false,
  bar: 'test', // βœ… no error (extra property allowed)
}))

// 2) Return annotation (strict): rejects extra props
const second = computed(
  (): FooBarInterface => ({
    foo: false,
    bar: 'test', // ❌ TS2353: Object literal may only specify known properties
  }),
)

Strict alternative with satisfies

To keep the concise generic style while getting strictness, suggest the satisfies operator:

const third = computed(() => ({
  foo: false,
  bar: 'test', // ❌ error: 'bar' is not in FooBarInterface
} satisfies FooBarInterface))

This enforces that the object conforms exactly to FooBarInterface without allowing extra properties.

Why this happens (TypeScript behavior)

This is not a Vue/runtime issue but a well-known TypeScript typing nuance:

  • Generics (computed<Foo>(...)) behave like a constraint: the inferred return just needs to be assignable to Foo. Excess property checks do not run for values inferred into a generic parameter.
  • Explicit return annotations ((): Foo => ...) do trigger excess property checks on object literals, so extra fields are rejected.

Because computed is a generic helper, this difference is unavoidable at the type-system level.

Proposed docs improvement

In the β€œTyping computed()” section, add a short note warning about this difference and showing the stricter alternatives.

Environment

  • Vue: 3.5.18
  • TypeScript: 5.9.2
  • Nuxt: 4.0.3

This request is strictly about improving documentation and developer ergonomics.

CesarWatrin avatar Aug 27 '25 13:08 CesarWatrin