我需要一个函数来接受具有以下限制的参数:
x
的成员即可。x
的成员,则它的类型必须为string
。如何键入类似的功能?
declare function foo<T /* extends ??? */ >(arg: T): boolean
使用条件类型,我可以正常工作,但是又出现了另一个问题。
type Constrained<T> = 'x' extends keyof T
? (T extends { x: string } ? T : never)
: T;
declare function foo<T>(a: Constrained<T>): boolean;
基本上,Constraint<T>
解析为never
,如果T
的成员名为x
而不是string
类型或解析为T
除此以外。然后,对带有“无效”对象的foo
的任何调用都将被拒绝,因为不能为never
分配任何内容(never
本身除外)。
效果很好...直到我遇到类似的东西
class SomeClass<U /* extends ??? */> {
prop!: U;
method() {
// Fails :(
// How to restrict U to allow this call?
foo(this.prop); // <-- Error: Argument of type 'U' is not
// assignable to parameter of
// type 'Constrained<U>'.
}
}
答案 0 :(得分:1)
除非您确实需要,否则我将倾向于避免此类复杂的约束。我的建议是从更原始的片段中构建您正在谈论的类型,例如:
type Unknown = string | number | boolean | symbol | null | void | object;
type Constraint = Exclude<Unknown, object> | { [k: string]: unknown, x?: string };
那个Constraint
只是一个普通的旧联合,它或多或少地代表了除string
键的值非x
以外的所有对象。完美吗?也许不是,但是处理起来要容易得多
declare function foo<T extends Constraint>(a: T): boolean;
class SomeClass<T extends Constraint> { /* ... */ };
foo(undefined); // okay
foo(null); // okay
foo("string"); // okay
foo(123); // okay
foo([]); // okay
foo([123]); // okay
foo([123, { x: "string" }]); // okay
foo(() => 123); // okay
foo({}); // okay
foo({ a: 123 }); // okay
foo({ a: 123, x: 123 }); // error
foo({ a: 123, x: { y: 123 } }); // error
foo(Math.random() < 0.5 ? 1 : { a: 123, x: "string" }); // okay
而且您在SomeClass
内部也再没有问题了:
class SomeClass<T extends Constraint> {
prop!: T;
method() {
foo(this.prop); // easy-peasy
}
}
如果您迫切需要循环约束或自引用约束,则可以安抚编译器,但这对我来说是一个反复试验的事情,并且一路走来也有很多陷阱。让我们从您的类型函数开始:
type Constrained<T> = 'x' extends keyof T
? (T extends { x: string } ? T : never)
: T;
您最初的foo
定义似乎起作用,但只能通过一些可能引起麻烦的type inference:
declare function foo<T>(a: Constrained<T>): boolean;
在T
类型为a
的情况下,编译器如何知道Constrained<T>
?它必须通过以某种方式查看条件类型,将Constrained<T>
视为T
的推理站点。我猜测,编译器看到Constrained<T>
可分配给never | T
的{{1}},因此推断T
与{ {1}}。无论如何,那很好。
做这种事情的一种更“官方支持”的方法是使T
的类型为a
,因为交集是known to serve as inference sites。这与您所说的完全一样,但会让我在晚上睡得更香:
a
对于类,您真正想做的事情会给您一个循环约束错误:
T & Constrained<T>
这可以通过添加一些伪类型参数并使用条件类型来解决,该条件类型的评估将推迟到具体实例化declare function foo<T>(a: T & Constrained<T>): boolean;
之前:
class SomeClass<T extends Constrained<T>> { /* ... * / } // error!
// Type parameter 'T' has a circular constraint.
编译器不再注意到圆度,但是它仍然存在。
该类的实现仍然会给您错误,恰恰是因为您正在延迟编译器对否则为圆形约束的检查,因此它不知道您在做什么将是安全的:
SomeClass
一种处理方法是使class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> { /* ... * / }
declare const ok: SomeClass<{ a: string }>; // okay
declare const alsoOk: SomeClass<{ x: string }>; // okay
declare const notOk: SomeClass<{ x: number }>; // error, number not a string
的类型为class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> {
prop!: T;
method() {
foo(this.prop); // still error
}
}
而不是prop
:
Constrained<T>
但是您仍然可能会在其他地方遇到其他此类问题,并且您最终可能只需要使用type assertions来使错误消失:
T
无论如何,您可以看到这真是一团糟。这就是为什么我仍然推荐最初的旧联盟解决方案的原因。
好的,希望能有所帮助。祝你好运!