Typescript - 如何缩小 switch 语句中泛型类型的可能性?
Typescript - How do I narrow type possibilities of a generic type in a switch statement?
我正在尝试编写一个函数,该函数将根据传递的键和参数执行特定的计算。我还想强制传递的键和参数之间的关系,所以我使用了一个带有约束的通用函数:
interface ProductMap {
one: {
basePrice: number;
discount: number;
},
two: {
basePrice: number;
addOnPrice: number;
}
}
function getPrice<K extends keyof ProductMap>(key: K, params: ProductMap[K]) {
switch (key) {
case 'one': {
return params.basePrice - params.discount; // Property 'discount' does not exist on type 'ProductMap[K]'.
}
case 'two': {
return params.basePrice + params.addOnPrice;
}
}
}
也许我以错误的方式思考这个问题,但似乎打字稿应该能够缩小 switch 语句中的泛型类型。我能让它工作的唯一方法就是这种尴尬:
function getPrice<K extends keyof ProductMap>(key: K, params: ProductMap[K]) {
switch (key) {
case 'one': {
const p = params as ProductMap['one'];
return p.basePrice - p.discount;
}
case 'two': {
const p = params as ProductMap['two'];
return p.basePrice + p.addOnPrice;
}
}
}
谁能解释为什么 #1 不起作用或提供替代解决方案?
确实,看起来 TypeScript 不是那么聪明,但是有一个解决方法,比转换更好:
function getPrice(productMap: ProductMap, key: keyof ProductMap) {
switch (key) {
case 'one': {
const params = productMap['one'];
return params.basePrice - params.discount;
}
case 'two': {
const params = productMap['two'];
return params.basePrice + params.addOnPrice;
}
}
}
"Can anyone explain why #1 won't work or offer an alternative solution?"
这就是 #1 不起作用的原因:Typescript 对 key
等变量具有 control-flow type narrowing,但对 K
.
等类型参数没有
case 'one':
检查将变量 key: K
的类型缩小为 key: 'one'
。
但是并没有从K extends 'one' | 'two'
缩小到K extends 'one'
,因为没有对实际的类型变量K
做测试,也做不了任何缩小它的测试。所以params: ProductMap[K]
仍然是params: ProductMap[K]
,K
仍然是同一个类型,所以params
的类型没有变窄。
这里有一个替代解决方案:使用判别联合,并打开判别式(即下面代码中的 __tag
属性)。
type ProductMap =
{
__tag: 'one';
basePrice: number;
discount: number;
} | {
__tag: 'two';
basePrice: number;
addOnPrice: number;
}
function getPrice(params: ProductMap): number {
switch (params.__tag) {
case 'one': {
return params.basePrice - params.discount;
}
case 'two': {
return params.basePrice + params.addOnPrice;
}
}
}
转换为特定类型是一种解决方案(但不是最好的):
interface ProductMap {
one: {
basePrice: number;
discount: number;
};
two: {
basePrice: number;
addOnPrice: number;
};
}
function getPrice<K extends keyof ProductMap>(
key: K,
_params: ProductMap[K]
) {
switch (key) {
case 'one': {
const params = _params as ProductMap['one'];
return params.basePrice - params.discount;
}
case 'two': {
const params = _params as ProductMap['two'];
return params.basePrice + params.addOnPrice;
}
}
}
并且,为了保持单个 return 类型 ,定义 函数 return 类型 :
interface ProductMap {
one: {
basePrice: number;
discount: number;
};
two: {
basePrice: number;
addOnPrice: number;
};
}
function getPrice<K extends keyof ProductMap>(
key: K,
_params: ProductMap[K]
): number {
switch (key) {
case 'one': {
const params = _params as ProductMap['one'];
return params.basePrice - params.discount;
}
case 'two': {
const params = _params as ProductMap['two'];
return params.basePrice + params.addOnPrice;
}
}
}
我正在尝试编写一个函数,该函数将根据传递的键和参数执行特定的计算。我还想强制传递的键和参数之间的关系,所以我使用了一个带有约束的通用函数:
interface ProductMap {
one: {
basePrice: number;
discount: number;
},
two: {
basePrice: number;
addOnPrice: number;
}
}
function getPrice<K extends keyof ProductMap>(key: K, params: ProductMap[K]) {
switch (key) {
case 'one': {
return params.basePrice - params.discount; // Property 'discount' does not exist on type 'ProductMap[K]'.
}
case 'two': {
return params.basePrice + params.addOnPrice;
}
}
}
也许我以错误的方式思考这个问题,但似乎打字稿应该能够缩小 switch 语句中的泛型类型。我能让它工作的唯一方法就是这种尴尬:
function getPrice<K extends keyof ProductMap>(key: K, params: ProductMap[K]) {
switch (key) {
case 'one': {
const p = params as ProductMap['one'];
return p.basePrice - p.discount;
}
case 'two': {
const p = params as ProductMap['two'];
return p.basePrice + p.addOnPrice;
}
}
}
谁能解释为什么 #1 不起作用或提供替代解决方案?
确实,看起来 TypeScript 不是那么聪明,但是有一个解决方法,比转换更好:
function getPrice(productMap: ProductMap, key: keyof ProductMap) {
switch (key) {
case 'one': {
const params = productMap['one'];
return params.basePrice - params.discount;
}
case 'two': {
const params = productMap['two'];
return params.basePrice + params.addOnPrice;
}
}
}
"Can anyone explain why #1 won't work or offer an alternative solution?"
这就是 #1 不起作用的原因:Typescript 对 key
等变量具有 control-flow type narrowing,但对 K
.
case 'one':
检查将变量 key: K
的类型缩小为 key: 'one'
。
但是并没有从K extends 'one' | 'two'
缩小到K extends 'one'
,因为没有对实际的类型变量K
做测试,也做不了任何缩小它的测试。所以params: ProductMap[K]
仍然是params: ProductMap[K]
,K
仍然是同一个类型,所以params
的类型没有变窄。
这里有一个替代解决方案:使用判别联合,并打开判别式(即下面代码中的 __tag
属性)。
type ProductMap =
{
__tag: 'one';
basePrice: number;
discount: number;
} | {
__tag: 'two';
basePrice: number;
addOnPrice: number;
}
function getPrice(params: ProductMap): number {
switch (params.__tag) {
case 'one': {
return params.basePrice - params.discount;
}
case 'two': {
return params.basePrice + params.addOnPrice;
}
}
}
转换为特定类型是一种解决方案(但不是最好的):
interface ProductMap {
one: {
basePrice: number;
discount: number;
};
two: {
basePrice: number;
addOnPrice: number;
};
}
function getPrice<K extends keyof ProductMap>(
key: K,
_params: ProductMap[K]
) {
switch (key) {
case 'one': {
const params = _params as ProductMap['one'];
return params.basePrice - params.discount;
}
case 'two': {
const params = _params as ProductMap['two'];
return params.basePrice + params.addOnPrice;
}
}
}
并且,为了保持单个 return 类型 ,定义 函数 return 类型 :
interface ProductMap {
one: {
basePrice: number;
discount: number;
};
two: {
basePrice: number;
addOnPrice: number;
};
}
function getPrice<K extends keyof ProductMap>(
key: K,
_params: ProductMap[K]
): number {
switch (key) {
case 'one': {
const params = _params as ProductMap['one'];
return params.basePrice - params.discount;
}
case 'two': {
const params = _params as ProductMap['two'];
return params.basePrice + params.addOnPrice;
}
}
}