【发布时间】:2021-10-18 20:28:56
【问题描述】:
我有一个用例,我有一个可组合对象,它应该(不)与某些功能一起使用,具体取决于它的组成部分。合成是使用类型交集完成的。
这是代码的简化版本:
type A<T> = { a: T }
type B<T> = { b: (val: T) => T }
const shouldWork = {
a: 'str',
b: (val: string) => val.toUpperCase(),
someOtherProp: 'foo'
}
const shouldFail = {
a: 'str',
b: (val: number) => 42
}
function test<T extends A<???> & B<???>>(obj: T): T {
return {
...obj,
a: obj.b(obj.a)
}
}
const res1 = test(shouldWork);
// res1 should be typeof shouldWork, i.e. A & B & { someOtherProp: string }
console.log(res1.a); // "STR"
console.log(res1.someOtherProp); // "foo"
const res2 = test(shouldFail); // should fail because A<string> and B<number> don't match
我尝试过使用test<T extends A<any> & B<any>>,但这当然允许任何泛型类型的组合。
然后我尝试添加一个额外的类型S 以确保两个泛型相同:test<S, T extends A<S> & B<S>>。但这会失败,因为 “参数类型 'val' 和 'val' 不兼容。类型 'unknown' 不能分配给类型 'string'”
经过多次尝试,我发现了一些可以按预期工作的东西:
function test<S, T extends A<S> & B<S> = A<S> & B<S>>(obj: T): T { ... }
const res1 = test<string>(shouldWork);
const res2 = test<string>(shouldFail); // throws error: Types of parameters 'val' and 'val' are incompatible. Type 'string' is not assignable to type 'number'
但正如您所见,它很难阅读,也很难写,尤其是当交集包含更多类型时。
有没有更简单的方法来完成这项工作?
【问题讨论】:
-
为什么还需要
T?你应该只使用S(重命名为There)。即使是示例用例也不能保证返回的值是正确的类型。如果交集包含很多类型,则给它一个别名,如type I<T>= A<T> & B<T> & C<T> & ...,然后只是function test<T>(obj: I<T>)。如果这些建议与您的用例不匹配,您可能需要详细说明并提供说明原因的minimal reproducible example。 -
但是现在你返回
A<T> & B<T>,这不是我想要的。我想返回与函数参数相同的类型。例子:如果你有type X = A & B & C,那么test应该返回A & B & C,即使test只需要A & B就可以工作。我将在问题中添加一个示例 -
好的,那么this 更适合你吗?
-
如果是这样我可以写一个答案;如果没有,请告诉我缺少什么。
-
我猜,你是个魔术师。我所有的测试都通过了。让我试着解释一下你的解决方案:"function
test接受一个可以是任何东西的参数(通用类型O没有约束)并且它必须实现I<T>(等于到A<T> & B<T>),并且函数返回该类型"。我不明白的是:为什么test<S, T extends A<S> & B<S>>(obj: T): T不起作用?也许您可以写一个简短的答案并获得当之无愧的积分。
标签: typescript generics type-inference typescript-generics inferred-type