# 213-VueBasicProps
# 题目描述
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.
# 分析
这题目比较长,而且依赖了 6-SimpleVue。
对于不了解 vue 的,我理了下题目要求:
除了 data, methods, computed 的定义,增加了 props 的类型定义,需要在 data, methods, computed 中,通过 this 访问到这个 props 定义的类型。
比如:
props: {
foo: { type: [Boolean, Number, String] }
}
那么在 data、methods、computed 中, `this.props.foo` 就可以访问到,并且类型是 `boolean | number | string`
而注入 this 的方法,在 6-SimpleVue 中已经讲过,那么这题的核心,就转换成了,如何把 foo: { type: [BooleanConstructor, NumberConstructor, StringConstructor] }
转换成 foo: boolean | number | string
。
等等,为什么是 BooleanConstructor
而不是 boolean 或者 Boolean?
这里就涉及到函数中的隐式类型推断了。
由于题目中,props 的类型是根据入参隐式推断出来的。js 中的 String,会被推断为 StringConstructor,这一点,好好理解下 js 中的 String 中的功能想必不难理解。
那么如何从 StringConstructor 得到 string?
type Cons<T> = T extends () => infer R ? R : never;
// string
type Case1 = Cons<StringConstructor>;
其本质,可以查看下 StringConstructor 的定义:
interface StringConstructor {
new (value?: any): String;
(value?: any): string;
readonly prototype: String;
fromCharCode(...codes: number[]): string;
}
借助第二个特性 (value?: any): string
,就可以匹配出来。
掌握了这一点,这一题就不在话下了,可以先实现一个转换 props 的类型:
type ClassToType<C> =
// 匹配 StringConstructor, NumberConstructor, BooleanConstructor etc.
C extends () => infer T
? T
: C extends unknown[]
? // 元组,递归每一个元素
// 此处借助了 C[number] 利用联合类型的分发特性遍历每一个元素
ClassToType<C[number]>
: // 匹配用户自己定义的 ClassA 这样的要求
C extends new (...args: any) => any // user defined constructors
? InstanceType<C>
: // 不应该出现其他情况
never;
type ComputedProps<P> = {
// 遍历 props 的属性
[key in keyof P]: P[key] extends { type: infer T } // 匹配 type: xxx 的情况
? ClassToType<T>
: {} extends P[key]
? // 如果是 {},对应 propA 的情况
any
: // 处理 propF: RegExp 的情况
ClassToType<P[key]>;
};
type d = ComputedProps<{
propA: {};
propB: { type: String };
propC: { type: Boolean };
propD: { type: ClassA };
propE: { type: [String, Number] };
propF: RegExp;
}>;
# 题解
完整题解如下:
type ComputedValues<C> = {
[key in keyof C]: C[key] extends (...args: unknown[]) => infer R ? R : never;
};
type ClassToType<C> = C extends () => infer T // String/Number/Boolean
? T
: C extends unknown[]
? ClassToType<C[number]>
: C extends new (...args: any) => any // user defined constructors
? InstanceType<C>
: never;
type ComputedProps<P> = {
[key in keyof P]: P[key] extends { type: infer T }
? ClassToType<T>
: {} extends P[key]
? any
: ClassToType<P[key]>;
};
declare function VueBasicProps<P, D, C, M>(options: {
props: P;
// 注入 props 的声明
data: (this: ComputedProps<P>) => D;
computed: C & ThisType<D & ComputedProps<P>>;
methods: M & ThisType<D & M & ComputedValues<C> & ComputedProps<P>>;
}): any;
# 知识点
- 隐式类型推断, String -> StringConstructor
- 同 6-SimpleVue