[英]Enforcing parameter combinations with typescript
I have the following code:我有以下代码:
enum ParamOneType {
Round,
Square,
}
interface PossibleValues {
[ParamOneType.Round]: 'a' | 'b';
[ParamOneType.Square]: 'c' | 'd';
}
const indexRound = {
a: 'whatever',
b: 'whatever',
};
const doSomething = <T extends ParamOneType>(
paramOne: T,
paramTwo: PossibleValues[T],
): void => {
switch (paramOne) {
case ParamOneType.Round: {
// Type 'PossibleValues[T]' cannot be used to index type '{ a: string; b: string; }'
const b = indexRound[paramTwo];
}
}
};
Why do I get the error Type 'PossibleValues[T]' cannot be used to index type '{ a: string; b: string; }'
为什么我会收到错误
Type 'PossibleValues[T]' cannot be used to index type '{ a: string; b: string; }'
Type 'PossibleValues[T]' cannot be used to index type '{ a: string; b: string; }'
Type 'PossibleValues[T]' cannot be used to index type '{ a: string; b: string; }'
here? Type 'PossibleValues[T]' cannot be used to index type '{ a: string; b: string; }'
在这里? Intellisense does seem to pick up the function signature correctly, for example my autocomplete in VSCode shows: Intellisense 似乎确实正确地选择了 function 签名,例如我在 VSCode 中的自动完成显示:
const doSomething: <ParamOneType.Round>(paramOne: ParamOneType.Round, paramTwo: "a" | "b") => void
when calling the function as doSomething(ParamOneType.Round, 'a')
当调用 function 作为
doSomething(ParamOneType.Round, 'a')
For this example, where you do a switch
on paramOne
, I'd stay away from generics , which cannot be narrowed by control flow analysis .对于这个例子,你在
paramOne
上做一个switch
,我会远离generics ,它不能被控制流分析缩小。 It's always possible that T
could be the full ParamOneType
type, at which point someone could call doSomething()
with parameters you don't expect, like doSomething( Math.random()<0.99? ParamOneType.Round: ParamOneType.Square, "c")
, which has a 99% chance of being bad. T
总是有可能是完整的ParamOneType
类型,此时有人可以使用您不期望的参数调用doSomething()
,例如doSomething( Math.random()<0.99? ParamOneType.Round: ParamOneType.Square, "c")
,它有 99% 的可能性是坏的。 This makes narrowing inside generic functions tricky.这使得缩小泛型函数内部变得棘手。 It's a known issue, and there are multiple feature requests for some way to improve it.
这是一个已知问题,并且有多个功能请求以某种方式改进它。 See microsoft/TypeScript#27808 or microsoft/TypeScript#33014 for more information.
有关详细信息,请参阅microsoft/ TypeScript#27808 或microsoft/TypeScript#33014 。
Instead, you can give doSomething
a rest parameter whose type is a discriminated union of rest tuple types , and immediately destructure into the two variables paramOne
and paramTwo
.相反,您可以给
doSomething
一个rest 参数,其类型是rest 元组类型的可区分联合,并立即解构为两个变量paramOne
和paramTwo
。
type DoSomethingParams =
[paramOne: ParamOneType.Round, paramTwo: "a" | "b"] |
[paramOne: ParamOneType.Square, paramTwo: "c" | "d"]
const doSomething = ([paramOne, paramTwo]: DoSomethingParams): void => { }
You can see that this behaves as desired from the caller's side:您可以从调用者的角度看到它的行为符合要求:
doSomething(ParamOneType.Round, "a") // okay
doSomething(ParamOneType.Round, "c") // error
doSomething(ParamOneType.Square, "c") // okay
doSomething(ParamOneType.Square, "b") // error
And since TypeScript 4.6 and above supports control flow analysis for destructured discriminated unions , you can switch on paramOne
in the implementation and the compiler will automatically narrow paramTwo
accordingly:并且由于 TypeScript 4.6 及更高版本支持对解构的可区分联合的控制流分析,您可以在实现中打开
paramOne
,编译器会自动缩小paramTwo
相应的范围:
const doSomething = (...[paramOne, paramTwo]: DoSomethingParams): void => {
switch (paramOne) {
case ParamOneType.Round: {
const b = indexRound[paramTwo]; // okay
}
}
};
Also note that you can generate the above DoSomethingParams
type programmatically from PossibleValues
, as follows:另请注意,您可以从
PossibleValues
以编程方式生成上述DoSomethingParams
类型,如下所示:
type DoSomethingParams = { [T in ParamOneType]:
[paramOne: T, paramTwo: PossibleValues[T]]
}[ParamOneType]
This is called a distributive object type (as coined in ms/TS#47109 ) where you immediately index into a mapped type to get a union of [paramOne: T, paramTwo: PossibleValues[T]]
for every T
in PossibleValues
.这称为分布式 object 类型(如在ms/TS#47109中创造的),您可以立即索引到映射类型以获得 [ paramOne
[paramOne: T, paramTwo: PossibleValues[T]]
的联合,用于每个T
中的PossibleValues
。
You can just add a type definition to indexRound
and your code should compile:您可以将类型定义添加到
indexRound
并且您的代码应该可以编译:
enum ParamOneType {
Round,
Square,
}
interface PossibleValues {
[ParamOneType.Round]: 'a' | 'b';
[ParamOneType.Square]: 'c' | 'd';
}
const indexRound: { [key: string]: string } = {
a: 'whatever',
b: 'whatever',
};
const doSomething = <T extends ParamOneType>(
paramOne: T,
paramTwo: PossibleValues[T],
): void => {
switch (paramOne) {
case ParamOneType.Round: {
// Type 'PossibleValues[T]' cannot be used to index type '{ a: string; b: string; }'
const b = indexRound[paramTwo];
}
}
};
While you could use the as
operator, this is usually considered bad practice.虽然您可以使用
as
运算符,但这通常被认为是不好的做法。 Also you should consider your code a little more since PossibleValues[T]
can potentially be c
or d
which cannot be indexed on your indexRound
object.此外,您应该多考虑一下您的代码,因为
PossibleValues[T]
可能是c
或d
,它们无法在您的indexRound
object 上编入索引。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.