yunliuyan / type-challenges

typescript-challenges
0 stars 2 forks source link

0015-hard-vue-basic-props #15

Open yunliuyan opened 11 months ago

yunliuyan commented 11 months ago

Vue Basic Props hard #vue #application

by Anthony Fu @antfu

Take the Challenge    日本語

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 }

test-case:

import type { Debug, Equal, Expect, IsAny } from '@type-challenges/utils'

class ClassA {}

VueBasicProps({
  props: {
    propA: {},
    propB: { type: String },
    propC: { type: Boolean },
    propD: { type: ClassA },
    propE: { type: [String, Number] },
    propF: RegExp,
  },
  data(this) {
    type PropsType = Debug<typeof this>
    type cases = [
      Expect<IsAny<PropsType['propA']>>,
      Expect<Equal<PropsType['propB'], string>>,
      Expect<Equal<PropsType['propC'], boolean>>,
      Expect<Equal<PropsType['propD'], ClassA>>,
      Expect<Equal<PropsType['propE'], string | number>>,
      Expect<Equal<PropsType['propF'], RegExp>>,
    ]

    // @ts-expect-error
    this.firstname
    // @ts-expect-error
    this.getRandom()
    // @ts-expect-error
    this.data()

    return {
      firstname: 'Type',
      lastname: 'Challenges',
      amount: 10,
    }
  },
  computed: {
    fullname() {
      return `${this.firstname} ${this.lastname}`
    },
  },
  methods: {
    getRandom() {
      return Math.random()
    },
    hi() {
      alert(this.fullname.toLowerCase())
      alert(this.getRandom())
    },
    test() {
      const fullname = this.fullname
      const propE = this.propE
      type cases = [
        Expect<Equal<typeof fullname, string>>,
        Expect<Equal<typeof propE, string | number>>,
      ]
    },
  },
})

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.


Back Share your Solutions Check out Solutions

Related Challenges

6・Simple Vue
yunliuyan commented 11 months ago
type InferComputed<C extends Record<string, any>> = { [K in keyof C]: ReturnType<C[K]> }

type Prop<T = any> = PropType<T> | { type?: PropType<T> }
type PropType<T> = PropConstructor<T> | PropConstructor<T>[]

type PropConstructor<T = any> =
  | { new (...args: any[]): T & object }
  | { (): T }

type InferPropType<P> = 
  P extends Prop<infer T>
    ? unknown extends T
      ? any
      : T
    : any 

type InferProps<P extends Record<string, any>> = 
  { [K in keyof P]: InferPropType<P[K]> }

declare function VueBasicProps<P, D, C extends Record<string, any>, M, Props = InferProps<P>>(
  options: {
    props?: P,
    data(this: Props): D,
    computed: C & ThisType<Props & D & InferComputed<C> & M>,
    methods: M & ThisType<Props & D & InferComputed<C> & M>
  }
): Props & D & InferComputed<C> & M