打字稿模板文字类型循环约束
Typescript template literal types circular constraint
我正在努力寻找一种在错误消息中显示类型参数的方法。这个想法是为了防止传递已经注入的依赖项,并在编译时检查它。
我是这样解决的:
export type TCons<T> = new (...args: any[]) => T
export interface Has<K extends string, T> {
get: (k: K, v: TCons<T>) => T
}
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Host extends Has<Key, P> ? `${Key} already exists` : Provider
>(
provider: P
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
export interface Application {
withSearchProvider: CombineExclusive<this, "SearchProvider", SearchProvider>
}
如果这样使用:
const liveApplication = (app: Application) =>
app
.withSearchProvider(new A())
.withSearchProvider(new B())
.withSearchProvider(new A())
您将在最后一行收到如下所示的错误:SearchProvider already exists
。
我想稍微改进一下:SearchProvider A already exists
这是我开始挣扎的地方:
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Host extends Has<Key, P> ? `${Key} ${P} already exists` : Provider
>(
provider: P
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
我无法在模板文字中引用 P,因为它创建了循环约束。另一种方法可能是引用“原始”P,但我不知道如何,或者是否可能。
所以,我的任务是创建一个类型约束,错误消息显示参数类型 P。有什么想法吗?
Link 到 ts playground: playground
TypeScript 有时接受循环引用,有时不接受。如果你有一个 generic function type and can't get a circular reference to be accepted inside a type parameter's constraint, you can sometimes move the reference out of the constraint and into a conditionally typed 函数参数。也就是说,从这样的事情:
function orig<T extends F<T>>(param: T) { } // error, circular constraint
像这样:
function fixed<T>(param: T extends F<T> ? T : F<T>) { } // okay
写成T extends F<T> ? T : F<T>
有点奇怪,但一般来说编译器会推断T
是param
的类型。因此,如果 T extends F<T>
符合要求,函数将看起来像 function fixed<T>(param: T) {}
并且不会有错误。另一方面,如果 T extends F<T>
不满足,则函数看起来像 function fixed<T>(param: F<T>)
并且由于 param
是 T
而不是 F<T>
类型,你'当您违反通用约束时,您会得到一个与您得到的错误非常相似的错误。
在您的示例中,这可以更改为:
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Provider
>(provider: P extends (Host extends Has<Key, P> ? never : unknown) ? P :
`${Key} of type '${Extract<P, { type: string }>['type']}' already exists`
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
稍微改了一下,还是一样的效果;如果 Host extends Has<Key, P>
为真,则 this 变为 P extends unknown ? P : `...`
变为 P
并且调用将成功。如果 Host extends Has<Key, P>
为 false,则这变为 P extends never ? P : `...`
变为 `...`
并且调用将失败,模板文字作为错误消息的一部分。
另请注意,您不能通过 `${P}`
将 P
序列化为字符串,因为 P
不是 string
/number
/boolean
/bigint
(根据ms/TS#40336的要求)。所以我采用 P
,它应该具有 string
值 type
属性,并将其放入消息中。
让我们看看实际效果:
const liveApplication = (app: Application) =>
app
.withSearchProvider(new A())
.withSearchProvider(new B())
.withSearchProvider(new A()) // error
// -------------------> ~~~~~~~
// Argument of type 'A' is not assignable to parameter of type
// '"SearchProvider of type 'a' already exists"'.(2345)
看起来不错!
我正在努力寻找一种在错误消息中显示类型参数的方法。这个想法是为了防止传递已经注入的依赖项,并在编译时检查它。
我是这样解决的:
export type TCons<T> = new (...args: any[]) => T
export interface Has<K extends string, T> {
get: (k: K, v: TCons<T>) => T
}
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Host extends Has<Key, P> ? `${Key} already exists` : Provider
>(
provider: P
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
export interface Application {
withSearchProvider: CombineExclusive<this, "SearchProvider", SearchProvider>
}
如果这样使用:
const liveApplication = (app: Application) =>
app
.withSearchProvider(new A())
.withSearchProvider(new B())
.withSearchProvider(new A())
您将在最后一行收到如下所示的错误:SearchProvider already exists
。
我想稍微改进一下:SearchProvider A already exists
这是我开始挣扎的地方:
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Host extends Has<Key, P> ? `${Key} ${P} already exists` : Provider
>(
provider: P
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
我无法在模板文字中引用 P,因为它创建了循环约束。另一种方法可能是引用“原始”P,但我不知道如何,或者是否可能。 所以,我的任务是创建一个类型约束,错误消息显示参数类型 P。有什么想法吗?
Link 到 ts playground: playground
TypeScript 有时接受循环引用,有时不接受。如果你有一个 generic function type and can't get a circular reference to be accepted inside a type parameter's constraint, you can sometimes move the reference out of the constraint and into a conditionally typed 函数参数。也就是说,从这样的事情:
function orig<T extends F<T>>(param: T) { } // error, circular constraint
像这样:
function fixed<T>(param: T extends F<T> ? T : F<T>) { } // okay
写成T extends F<T> ? T : F<T>
有点奇怪,但一般来说编译器会推断T
是param
的类型。因此,如果 T extends F<T>
符合要求,函数将看起来像 function fixed<T>(param: T) {}
并且不会有错误。另一方面,如果 T extends F<T>
不满足,则函数看起来像 function fixed<T>(param: F<T>)
并且由于 param
是 T
而不是 F<T>
类型,你'当您违反通用约束时,您会得到一个与您得到的错误非常相似的错误。
在您的示例中,这可以更改为:
type CombineExclusive<Host, Key extends string, Provider> = <
P extends Provider
>(provider: P extends (Host extends Has<Key, P> ? never : unknown) ? P :
`${Key} of type '${Extract<P, { type: string }>['type']}' already exists`
) => Host extends Has<Key, P> ? never : Host & Has<Key, P>
稍微改了一下,还是一样的效果;如果 Host extends Has<Key, P>
为真,则 this 变为 P extends unknown ? P : `...`
变为 P
并且调用将成功。如果 Host extends Has<Key, P>
为 false,则这变为 P extends never ? P : `...`
变为 `...`
并且调用将失败,模板文字作为错误消息的一部分。
另请注意,您不能通过 `${P}`
将 P
序列化为字符串,因为 P
不是 string
/number
/boolean
/bigint
(根据ms/TS#40336的要求)。所以我采用 P
,它应该具有 string
值 type
属性,并将其放入消息中。
让我们看看实际效果:
const liveApplication = (app: Application) =>
app
.withSearchProvider(new A())
.withSearchProvider(new B())
.withSearchProvider(new A()) // error
// -------------------> ~~~~~~~
// Argument of type 'A' is not assignable to parameter of type
// '"SearchProvider of type 'a' already exists"'.(2345)
看起来不错!