Est*_*ask 4 typescript typescript-generics typescript-typings
该问题似乎特定于如何strictFunctionTypes影响泛型类类型。
这是一个密切复制发生的事情并且由于要求而无法进一步简化的类,any用于指定不设置额外限制的部分(操场):
class Foo<T> {
static manyFoo(): Foo<any[] | { [s: string]: any }>;
static manyFoo(): Foo<any[]> {
return ['stub'] as any;
}
barCallback!: (val: T) => void;
constructor() {
// get synchronously from elsewhere
(callback => {
this.barCallback = callback;
})((v: any) => {});
}
baz(callback: ((val: T) => void)): void {}
}
Run Code Online (Sandbox Code Playgroud)
TbarCallback签名中的泛型类型导致类型错误:
(method) Foo<T>.manyFoo(): Foo<any[]>
This overload signature is not compatible with its implementation signature.(2394)
Run Code Online (Sandbox Code Playgroud)
仅当在函数类型中T用作val类型时才会出现问题barCallback。
如果用作barCallback或baz不用T作参数类型,它就会消失:
barCallback!: (val: any) => void | T;
Run Code Online (Sandbox Code Playgroud)
如果没有manyFoo方法重载或签名不那么多样化,它就会消失。
如果barCallback在类中具有方法签名,则不会出现,但这会阻止稍后对其进行分配:
barCallback!(val: T): void;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,严格val类型并不重要,可以牺牲。由于barCallback不能用类中的方法签名替换,接口合并似乎是一种在不进一步放松类型的情况下抑制错误的方法:
interface Foo<T> {
barCallback(val: T): void;
}
Run Code Online (Sandbox Code Playgroud)
在与此类似的情况下,是否还有其他可能的解决方法?
我很欣赏为什么val: T在函数类型中以这种方式影响类类型的解释。
Tit*_*mir 19
这是一个差异问题的核心。所以首先是方差底漆:
给定一个泛型类型Foo<T>和两个相关类型Animal和Dog extends Animal。Foo<Animal>和之间有四种可能的关系Foo<Dog>:
Foo<Animal>和Foo<Dog>因为它为Animal和Dog,所以Foo<Dog>是一个子类型的Foo<Animal>,这也意味着Foo<Dog>可分配给Foo<Animal>type CoVariant<T> = () => T
declare var coAnimal: CoVariant<Animal>
declare var coDog: CoVariant<Dog>
coDog = coAnimal; //
coAnimal = coDog; // ?
Run Code Online (Sandbox Code Playgroud)
Foo<Animal>andFoo<Dog>就像它对Animaland 一样Dog,所以Foo<Animal>实际上是 的子类型Foo<Dog>,这也意味着Foo<Animal>可分配给Foo<Dog>type ContraVariant<T> = (p: T) => void
declare var contraAnimal: ContraVariant<Animal>
declare var contraDog: ContraVariant<Dog>
contraDog = contraAnimal; // ?
contraAnimal = contraDog; //
Run Code Online (Sandbox Code Playgroud)
Dog和Animal是相关的Foo<Animal>并且Foo<Dog>它们之间没有任何关系,因此两者都不可分配给另一个。type InVariant<T> = (p: T) => T
declare var inAnimal: InVariant<Animal>
declare var inDog: InVariant<Dog>
inDog = inAnimal; //
inAnimal = inDog; //
Run Code Online (Sandbox Code Playgroud)
Dog和Animal相关,则两者Foo<Animal>都是 的子类型Foo<Dog>并且Foo<Animal>是 的子类型Foo<Dog>意思是任何一种类型都可以分配给另一个。在更严格的类型系统中,这将是一种病态的情况,T实际上可能不会使用,但在打字稿中,方法参数位置被认为是双变量的。
class BiVariant<T> { m(p: T): void {} }
declare var biAnimal: BiVariant<Animal>
declare var biDog: BiVariant<Dog>
biDog = biAnimal; // ?
biAnimal = biDog; // ?
Run Code Online (Sandbox Code Playgroud)
所以问题是如何T影响方差的使用?在打字稿中,类型参数的位置决定了方差,一些例子:
T用作字段或函数的返回类型T用作函数签名的参数strictFunctionTypesT用于协变和逆变位置T用作下方法定义strictFunctionTypes的参数,或者用作方法或函数的参数类型(如果strictFunctionTypes关闭)。这里strictFunctionTypes解释了方法和函数参数的不同行为的原因:
更严格的检查适用于所有函数类型,除了那些源自方法或构造函数声明的函数类型。专门排除方法以确保通用类和接口(例如 Array)继续主要协变相关。严格检查方法的影响将是更大的破坏性变化,因为大量泛型类型将变得不变(即使如此,我们可能会继续探索这种更严格的模式)。
所以让我们看看, 的使用如何T影响 的方差Foo。
barCallback!: (val: T) => void; - 用作函数成员中的参数 -> 反变量位置
baz(callback: ((val: T) => void)): void- 用作另一个函数的回调参数中的参数。这有点棘手,剧透警告,这将证明是协变的。让我们考虑这个简化的例子:
type FunctionWithCallback<T> = (cb: (a: T) => void) => void
// FunctionWithCallback<Dog> can be assigned to FunctionWithCallback<Animal>
let withDogCb: FunctionWithCallback<Dog> = cb=> cb(new Dog());
let aliasDogCbAsAnimalCb: FunctionWithCallback<Animal> = withDogCb; // ?
aliasDogCbAsAnimalCb(a => a.animal) // the cb here is getting a dog at runtime, which is fine as it will only access animal members
let withAnimalCb: FunctionWithCallback<Animal> = cb => cb(new Animal());
// FunctionWithCallback<Animal> can NOT be assigned to FunctionWithCallback<Dog>
let aliasAnimalCbAsDogCb: FunctionWithCallback<Dog> = withAnimalCb; //
aliasAnimalCbAsDogCb(d => d.dog) // the cb here is getting an animal at runtime, which is bad, since it is using `Dog` members
Run Code Online (Sandbox Code Playgroud)
在第一个示例中,我们传递给的回调aliasDogCbAsAnimalCb期望接收一个Animal,因此它只使用Animal成员。实现withDogCb将创建一个Dog并将其传递给回调,但这很好。仅使用它期望的基类属性,回调将按预期工作。
在第二个示例中,我们传递给的回调aliasAnimalCbAsDogCb期望接收 a Dog,因此它使用Dog成员。但是实现withAnimalCb将传递给回调一个动物的实例。这可能会导致运行时错误,因为回调最终会使用不存在的成员。
所以给它仅仅是安全的,分配FunctionWithCallback<Dog>到FunctionWithCallback<Animal>,我们在地得出结论的这种用法T决定协方差。
所以我们T在 中同时使用了协变和逆变位置Foo,这意味着在 中Foo是不变的T。这意味着就类型系统而言,Foo<any[] | { [s: string]: any }>和Foo<any[]>实际上是不相关的类型。虽然重载在他们的检查中更宽松,但他们确实希望重载的返回类型和实现相关(实现返回或重载返回必须是另一个的子类型,例如)
为什么一些改变使它起作用:
strictFunctionTypes将使barCallback站点成为T双Foo变的,因此将是协变的barCallback为方法,使站点成为T双变量,因此Foo将是协变的barCallback将删除逆变使用,因此Foo将是协变的baz将删除的协的使用T使得Foo在逆变T。您可以继续strictFunctionTypes并为这个回调创建一个异常以保持双变量,通过使用双变量 hack(此处针对更窄的用例进行了解释,但适用相同的原则):
type BivariantCallback<C extends (... a: any[]) => any> = { bivarianceHack(...val: Parameters<C>): ReturnType<C> }["bivarianceHack"];
class Foo<T> {
static manyFoo(): Foo<any[] | { [s: string]: any }>;
static manyFoo(): Foo<any[]> {
return ['stub'] as any;
}
barCallback!: BivariantCallback<(val: T) => void>;
constructor() {
// get synchronously from elsewhere
(callback => {
this.barCallback = callback;
})((v: any) => {});
}
baz(callback: ((val: T) => void)): void {}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
352 次 |
| 最近记录: |