我倾向于远离像这样复杂的约束,除非你真的需要它。我的建议是从更原始的作品中建立你所说的类型,比如:
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>
? 它必须治疗
T
,通过某种方式看穿条件类型。我
猜测
编译器看到了
约束<T>
可分配给
never | T
,由此推断
类型与
A.
. 不管怎样,那很好。
A.
类型
T & Constrained<T>
known to serve as inference sites
. 这和你说的完全一样,但会让我晚上睡得更香:
declare function foo<T>(a: T & Constrained<T>): boolean;
至于上课,你真正关心的是
to do会产生循环约束错误:
class SomeClass<T extends Constrained<T>> { /* ... * / } // error!
// Type parameter 'T' has a circular constraint.
这可以通过添加一些伪类型参数和使用一个条件类型来解决,条件类型的计算将推迟到
某类
具体来说:
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
类型
约束<T>
而不是
:
class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> {
prop!: Constrained<T>;
method() {
foo(this.prop); // okay now
}
}
但你仍有可能在其他地方遇到其他类似的问题,最终你可能不得不随波逐流
type assertions
要消除错误:
class SomeClass<T extends (U extends any ? Constrained<T> : unknown), U = any> {
prop!: T;
method() {
foo(this.prop as Constrained<T>); // I know what I'm doing!
}
}
不管怎样,你可以看到这是多么混乱。这就是为什么我仍然推荐最初的普通老工会解决方案。
好吧,希望能有帮助。祝你好运!