TypeScript 中由泛型类型组成的对象(类型递归)
Objects composed of generic types in TypeScript (type recursion)
给出以下未类型化的 TS:
const compose = (thunk: any): any => {
const res = { ...thunk() };
return { ...res, then: (f: any): any => compose(() => ({...res, ...f()})) };
};
我们可以用它来制作可组合的对象:
const { foo, bar } = compose(() => ({foo: 1})).then(() => ({bar: 2}))
// foo: 1, bar: 2
但是在 TS 中输入这个似乎很棘手,因为类型是递归的。
我能想到的最好的是:
type Compose<T> = (thunk: () => T) => T & { then: Compose<any> };
const compose2 = <T extends {}>(thunk: () => T): ReturnType<Compose<T>> => {
const res = { ...thunk() };
return { ...res, then: (f) => compose2(() => ({ ...res, ...f() })) };
};
这意味着落在 compose2
之外的所有对象都是 any
类型的。
我想要完成的最终结果是使用所有组合对象键入的内容:
const combined = compose(() => ({foo: 1})).then(() => ({bar: 2}))
// type of combined: { foo: number } & { bar: number } & { then: … }
在我看来,我们需要某种可以打上递归结的 Fix
类型,因为 Compose
中 then
的类型需要递归。
当然,如果我们反转compose
的签名并以某种方式使用CPS,也许有办法做到这一点。我愿意接受建议!
请注意,2 进制 compose
,我们称之为 combine
,没有问题:
const combine = <A extends {}, B extends {}>(a: () => A, b: () => B): A & B => ({...a(), ...b()});
const bar = combine(() => ({foo: 1}), () => combine(() => ({bar: 2}), () => ({baz: 3})) )
但是编写语句并不是特别好,所以我希望从结果对象传递一个闭包,这样我就不必嵌套重复的函数调用。
我想你可能正在寻找这个:
type Compose<O extends object = {}> =
<T extends object>(thunk: () => T) => T & O & {
then: Compose<T & O>
}
const compose: Compose = (thunk) => {
const res = { ...thunk() };
return { ...res, then: f => compose(() => ({ ...res, ...f() })) };
};
then()
的 return 类型带有一些您需要在您的组合类型中表示的状态信息。如果我们将 O
视为“当前状态对象”,那么 Compose<O>
是一个通用函数,它接受类型为 () => T
的 thunk
用于任何其他对象类型 T
,而return是一个T & O & {then: Compose<T & O>}
...即新对象是T
和O
的交集,其then()
方法有T & O
作为新状态。
compose()
类型检查的实施是一个好兆头。让我们验证编译器是否理解调用的工作原理:
const { foo, bar } = compose(() => ({ foo: 1 })).then(() => ({ bar: "hello" }));
console.log(foo.toFixed(2)) // 1.00
console.log(bar.toUpperCase()); // HELLO
看起来不错!
给出以下未类型化的 TS:
const compose = (thunk: any): any => {
const res = { ...thunk() };
return { ...res, then: (f: any): any => compose(() => ({...res, ...f()})) };
};
我们可以用它来制作可组合的对象:
const { foo, bar } = compose(() => ({foo: 1})).then(() => ({bar: 2}))
// foo: 1, bar: 2
但是在 TS 中输入这个似乎很棘手,因为类型是递归的。
我能想到的最好的是:
type Compose<T> = (thunk: () => T) => T & { then: Compose<any> };
const compose2 = <T extends {}>(thunk: () => T): ReturnType<Compose<T>> => {
const res = { ...thunk() };
return { ...res, then: (f) => compose2(() => ({ ...res, ...f() })) };
};
这意味着落在 compose2
之外的所有对象都是 any
类型的。
我想要完成的最终结果是使用所有组合对象键入的内容:
const combined = compose(() => ({foo: 1})).then(() => ({bar: 2}))
// type of combined: { foo: number } & { bar: number } & { then: … }
在我看来,我们需要某种可以打上递归结的 Fix
类型,因为 Compose
中 then
的类型需要递归。
当然,如果我们反转compose
的签名并以某种方式使用CPS,也许有办法做到这一点。我愿意接受建议!
请注意,2 进制 compose
,我们称之为 combine
,没有问题:
const combine = <A extends {}, B extends {}>(a: () => A, b: () => B): A & B => ({...a(), ...b()});
const bar = combine(() => ({foo: 1}), () => combine(() => ({bar: 2}), () => ({baz: 3})) )
但是编写语句并不是特别好,所以我希望从结果对象传递一个闭包,这样我就不必嵌套重复的函数调用。
我想你可能正在寻找这个:
type Compose<O extends object = {}> =
<T extends object>(thunk: () => T) => T & O & {
then: Compose<T & O>
}
const compose: Compose = (thunk) => {
const res = { ...thunk() };
return { ...res, then: f => compose(() => ({ ...res, ...f() })) };
};
then()
的 return 类型带有一些您需要在您的组合类型中表示的状态信息。如果我们将 O
视为“当前状态对象”,那么 Compose<O>
是一个通用函数,它接受类型为 () => T
的 thunk
用于任何其他对象类型 T
,而return是一个T & O & {then: Compose<T & O>}
...即新对象是T
和O
的交集,其then()
方法有T & O
作为新状态。
compose()
类型检查的实施是一个好兆头。让我们验证编译器是否理解调用的工作原理:
const { foo, bar } = compose(() => ({ foo: 1 })).then(() => ({ bar: "hello" }));
console.log(foo.toFixed(2)) // 1.00
console.log(bar.toUpperCase()); // HELLO
看起来不错!