#213Hard

Vue Basic Props

Extend Simple Vue with a props option: infer prop types from constructors like Boolean, String or custom classes, including unions from constructor arrays.

This challenge continues from 6 - Simple Vue, you should finish that one first, and modify your code based on it to start this challenge. On top of the data/computed/methods trio, the options object now gets a props field: an object whose keys become real props injected into this, accessible in data, computed and methods. Each prop is declared either directly as a constructor or as an object with a type field containing one or more constructors, and your job is to translate those runtime values into compile-time types.

props: {
  foo: Boolean
}
// or
props: {
  foo: { type: Boolean }
}

should be inferred to type Props = { foo: boolean }.

When passing multiple constructors, the type should be inferred to a union:

props: {
  foo: { type: [Boolean, Number, String] }
}
// -->
type Props = { foo: boolean | number | string }

When an empty object is passed, the key should be inferred to any. (required, default, and array props in Vue are not considered in this challenge.)

Challenge Instructions: Vue Basic Props

Hard

This challenge continues from 6 - Simple Vue, you should finish that one first, and modify your code based on it to start this challenge*.

In addition to the Simple Vue, we are now having a new props field in the options. This is a simplified version of Vue's props option. Here are some of the rules.

props is an object containing each field as the key of the real props injected into this. The injected props will be accessible in all the context including data, computed, and methods.

A prop will be defined either by a constructor or an object with a type field containing constructor(s).

For example

props: {
foo: Boolean
}
// or
props: {
foo: { type: Boolean }
}

should be inferred to type Props = { foo: boolean }.

When passing multiple constructors, the type should be inferred to a union.

props: {
foo: { type: [Boolean, Number, String] }
}
// -->
type Props = { foo: boolean | number | string }

When an empty object is passed, the key should be inferred to any.

For more specified cases, check out the Test Cases section.

required, default, and array props in Vue are not considered in this challenge.

View on GitHub: https://tsch.js.org/213

Change the following code to make the test cases pass (no type check errors).

👋 Lifetime-License is leaving on August 10, 2026

Get it now for $29

One-time payment. Lifetime access to all pro challenges.

Loading...

Detailed Explanation

The complete solution:

type ExtractInstance<T> = T extends (...args: any[]) => infer R
  ? R
  : T extends new (...args: any[]) => infer R
    ? R
    : any
 
type InferProp<T> = T extends { type: infer U }
  ? U extends unknown[]
    ? ExtractInstance<U[number]>
    : ExtractInstance<U>
  : ExtractInstance<T>
 
type InferProps<TProps> = {
  [K in keyof TProps]: InferProp<TProps[K]>
}
 
type GetComputed<TComputed> = {
  [K in keyof TComputed]: TComputed[K] extends () => infer Result
    ? Result
    : never
}
 
declare function VueBasicProps<TProps, TData, TComputed, TMethods>(options: {
  props: TProps
  data: (this: InferProps<TProps>) => TData
  computed: TComputed & ThisType<TData & InferProps<TProps>>
  methods: TMethods &
    ThisType<TData & InferProps<TProps> & GetComputed<TComputed> & TMethods>
}): any

The SimpleVue skeleton is unchanged. All the new work happens in turning a props declaration into a props type, so start with the smallest piece.

ExtractInstance: from constructor to instance type

A prop is declared with a runtime value like String or ClassA. The type of the value String is StringConstructor, and the type of the value ClassA is typeof ClassA. Both are things you can invoke, and we want what invoking them produces.

The order of the two checks is the subtle part:

A quick sanity check:

type A = ExtractInstance<StringConstructor> // string (call signature wins)
type B = ExtractInstance<typeof ClassA>     // ClassA (construct signature)
type C = ExtractInstance<{}>                // any (fallback)

InferProp: the three declaration shapes

InferProp dispatches on how the prop was written:

InferProps then maps InferProp over every key, turning the whole declaration object into { propA: any; propB: string; ... }.

Wiring props into every context

Compared to Simple Vue, each this gains InferProps<TProps>:

Edge cases the tests cover

The takeaway: one careful conditional type (ExtractInstance) plus distribution over unions is all it takes to bridge Vue's runtime prop declarations into precise static types.

This challenge is originally from here.

Share this challenge

Learn the Concepts