我可以使用条件类型在接口上有条件地包含方法:
interface Iter<T> {
flatten: T extends Iterable<infer A> ? () => Set<A> : never;
}
是否有一种方法可以用类来做这件事?
假设您正在使用--strict
套件的编译器功能为"标准";安全级别,或者至少是--strictPropertyInitialization
编译器选项,那么您需要初始化您拥有的任何类成员。
但是编译器基本上不可能验证一个值是否可赋值给依赖于泛型类型参数的条件类型;它只是延迟对类型的求值,直到指定了泛型类型参数。因此,像T extends Iterable<infer A> ? () => Set<A> : never;
这样的东西对编译器来说是不透明的,所以如果不使用类型断言来平息编译器对类型安全的关注,你就不能给flatten
分配任何东西。
所以你可以这样做:
class Iter<T> {
constructor(public prop: T) { }
flatten = (function (this: Iter<T>) {
return new Set(this.prop as Iterable<any>)
} as T extends Iterable<infer A> ? () => Set<A> : never;
}
作品:
const a = new Iter(123);
try {
const oops = a.flatten(); // error!
// ----------> ~~~~~~~
// never has no call signatures
} catch (e) {
console.log(e); // this.prop is not iterable
}
const b = new Iter([1, 2, 3]);
const okay = b.flatten();
// const okay: Set<number>
,但它很笨拙和奇怪,甚至除了类型断言。你不会真的想让flatten
成为一个实例属性吧?它更像是一个存在于类原型上的方法。你是在对编译器撒谎,因为当T
不可迭代时,你又声称flatten
是never
类型的,这是不可能的。
相反,我认为你真正想要的是flatten
是一个始终存在的方法,但只有当T
是可迭代的时候才可以安全调用。您可以使用this
参数:
class Iter<T> {
constructor(public prop: T) { }
flatten<A>(this: Iter<Iterable<A>>) {
return new Set(this.prop)
}
}
编译时不需要类型断言或条件类型,并由编译器验证为安全的。在可赋值给Iter<Iterable<A>>
的Iter<T>
上调用flatten
时,返回类型是Set<A>
。
这在调用方方面的行为类似,并且错误更能描述问题(不是flatten
是never
,而是a
不是Iter<Iterable<A>>
):
const a = new Iter(123);
try {
const oops = a.flatten(); // error!
// --------> ~
// The 'this' context of type 'Iter<number>' is not assignable
// to method's 'this' of type 'Iter<Iterable<unknown>>'.
} catch (e) {
console.log(e); // this.prop is not iterable
}
const b = new Iter([1, 2, 3]);
const okay = b.flatten();
// const okay: Set<number>
Playground链接到代码