携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
困难
简单的 Vue 类型
/** 获取 computed 中的 this */
type ComputedData<T> = {
[P in keyof T]: T[P] extends () => infer R ? R : T[P]
}
type Option<D, C, M, Data = D extends () => infer R ? R : never> = {
data: D,
computed: C & ThisType<Data>,
methods: M & ThisType<M & Data & ComputedData<C>>,
} & ThisType<never>
declare function SimpleVue<D, C, M>(options: Option<D, C, M>): any
// 使用示例
SimpleVue({
data() {
// @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.amount)
alert(this.fullname.toLowerCase())
alert(this.getRandom())
},
test() {
const fullname = this.fullname
const cases: [Expect<Equal<typeof fullname, string>>] = [] as any
},
},
})
为了设置不同区域的 this 对象的类型,需要用到内置工具类型ThisType,相关介绍也可以看看我的笔记。
首先了解一下ThisType
的用法,下面定义了一段代码:
interface Data {
x: number,
y: number
}
type Obj = {
data: Data
add: () => number
}
const obj: Obj = {
data: {
x: 1,
y: 2,
},
add() {
// 这里 this 就是 obj 对象,类型则对应 Obj
return this.data.x + this.data.y;
}
}
但是我想改变一下这个this
对象的类型,那么我可以这么做:
// 使用内置工具类型 ThisType 声明上下文 this 的类型
type Obj = {
data: Data
add: () => number
} & ThisType<Data>
const obj: Obj = {
data: {
x: 1,
y: 2,
},
add() {
// 此时 this 的类型已经是 Data 了
return this.x + this.y;
}
}
需要注意的是,使用 ThisType 需要开启配置 noImplicitThis。
弄明白 ThisType 后,再回到上面的答案中,就能明白是怎么回事了:
/*
data 是函数,computed 和 methods 都是对象。
所以,针对不同范围的上下文,需要单独处理。
data 的上下文对象是 options
computed 中函数的上下文对象是 options.computed
methods 中函数的上下文对象是 options.methods
所以使用 ThisType 给对应的对象进行处理即可
*/
type Option<D, C, M, Data = D extends () => infer R ? R : never> = {
data: D,
computed: C & ThisType<Data>,
methods: M & ThisType<M & Data & ComputedData<C>>,
} & ThisType<never>
/*
定义泛型 D、C、M 用来获取 data、
computed、methods 的类型
*/
declare function SimpleVue<D, C, M>(options: Option<D, C, M>): any
柯里化 1
// 组装函数
type Medium<T, R> =
T extends [infer F, ...infer E]
? (arg: F) => Medium<E, R>
: R;
declare function Currying<T>(fn: T):
T extends (...args: infer Args) => infer R
? Medium<Args, R>
: never
// 使用示例:
const add = (a: number, b: number) => a + b
const three = add(1, 2)
const curriedAdd = Currying(add)
const five = curriedAdd(2)(3)
- 在条件类型中使用
infer
获取函数的参数和返回值 - 然后遍历数组
Args
,使用递归的方式组装函数即可
一开始我是这么来获取函数返回值的类型的:
declare function Currying<T extends any[], R>(fn: (...args: T) => R): Medium<T, R>
/*
得到的结果如下,最后的返回值不够准确,不是 true
const curried1: (arg: string) => boolean
*/
const curried1 = Currying((a: string) => true)
// 后面我换了种方式再获取函数的返回值
declare function Currying<T extends (...args: any[]) => unknown>(fn: T): ReturnType<T>
// curried1 的类型还是 boolean..
const curried1 = Currying((a: string) => true)
后来是去到解答区看到了这个解答,才知道要把这个类型推导放到最终结果处去进行处理:
declare function Currying<T>(fn: T):
T extends (...args: any[]) => infer R ? R : never
// 这次 curried1 的类型是 true 了,获得了具体的字面量类型
const curried1 = Currying((a: string) => true)
看了解答区的说明后,认为问题是出在函数的定义上:
// 3种读取函数的返回值的测试代码:
declare function f1(a: string): true // 函数声明
declare var f2: (a: string) => true // 匿名函数表达式
// 第一种,通过泛型在参数中定义。
declare function test1<R>(fn: (...args: any[]) => R): R;
test1(f1) // true ✅
test1(f2) // true ✅
test1((a: string) => true) // boolean ❌
// 第二种,通过泛型约束上去定义。
declare function test2<F extends (...args: any[]) => any>(fn: F): ReturnType<F>;
test2(f1) // true ✅
test2(f2) // true ✅
test2((a: string) => true) // boolean ❌
// 第三种,在泛型最终应用结果处再做判定。可以看出只有这种方法的效果最好,也是符合测试case的要求
declare function test3<F>(fn: F): F extends (...args: any[]) => any ? ReturnType<F> : never;
test3(f1) // true ✅
test3(f2) // true ✅
test3((a: string) => true) // true ✅