Simulates the derivation of the`props’ type in the vue3.0rfcs`createacomponent`api

  html5, javascript, typescript, vue.js

Type derivation in rfcType Inference

Expected effect

createComponent({
  props: {
    foo: {
      type: String,
      required: true
    },
    bar: {
      type: Number
    },
    boo: Boolean,
    options: (null as any) as { msg: string },
    requiredOptions: {
      type: (null as any) as { msg: string },
      required: true
    }
  } as const,
  setup(props) {
    props.foo; // string
    props.bar; // number | undefined
    props.boo; // boolean | undefined
    props.options; // {msg: string } | undefined
    props.requiredOptions; // {msg: string }
  }
});

String->stringNumber->numberBoolean->boolean

In ts

  • StringThe corresponding type isStringConstructor
  • NumberThe corresponding type isNumberConstructor
  • BooleanThe corresponding type isBooleanConstructor

However, what we want to achieve is to convert to lowercasestring | number | boolean

So we write a generic to convert

type NormalizeType<T> = T extends StringConstructor
  ? string
  : T extends NumberConstructor
  ? number
  : T extends BooleanConstructor
  ? boolean
  : T;

Playground preview link

Defines the type of prop

type BuiltInType<T> =
  | StringConstructor
  | NumberConstructor
  | BooleanConstructor
  | T;

Leaving a generic is compatible with complex types
Rfc complex prop type

DefinitioncreateComponentFunction receivedpropsType

type DefaultType<T> = {
  [key: string]:
    | {
        type?: BuiltInType<T>;
        require?: boolean;
      }
    | BuiltInType<T>;
};

The most critical step is based on the inputpropsType is calculatedsetupThe formal parameters received by the functionpropsType

type ReflexType<T> = {
  [key in keyof T]: T[key] extends { type: infer TYPE; required: true }
    ? NormalizeType<TYPE>
    : T[key] extends { type: infer TYPE }
    ? NormalizeType<TYPE> | undefined
    : NormalizeType<T[key]> | undefined
};

Put them togethercreateComponentfunction definition

function createComponent<T extends DefaultType<any>>(props: {
  props: T;
  setup(props: ReflexType<T>): any;
}) {}

Complete code

type BuiltInType<T> =
  | StringConstructor
  | NumberConstructor
  | BooleanConstructor
  | T;

type NormalizeType<T> = T extends StringConstructor
  ? string
  : T extends NumberConstructor
  ? number
  : T extends BooleanConstructor
  ? boolean
  : T;

type ReflexType<T> = {
  [key in keyof T]: T[key] extends { type: infer TYPE; required: true }
    ? NormalizeType<TYPE>
    : T[key] extends { type: infer TYPE }
    ? NormalizeType<TYPE> | undefined
    : NormalizeType<T[key]> | undefined
};

type DefaultType<T> = {
  [key: string]: { type?: BuiltInType<T>; require?: boolean } | BuiltInType<T>;
};

function createComponent<T extends DefaultType<any>>(props: {
  props: T;
  setup(props: ReflexType<T>): any;
}) {}

createComponent({
  props: {
    foo: {
      type: String,
      required: true
    },
    bar: {
      type: Number
    },
    boo: Boolean,
    options: (null as any) as { msg: string },
    requiredOptions: {
      type: (null as any) as { msg: string },
      required: true
    }
  } as const,
  setup(props) {
    props.foo;
    props.bar;
    props.boo;
    props.options;
    props.requiredOptions;
  }
});

Playground preview link

Renderings