typescript - TypeScript: generically infer union type member based on a string literal property
问题描述
TypeScript (v3.2.2) allows me to define a union of interfaces, each with a unique string literal property which can be used as a type guard, e.g.
type Device = Laptop | Desktop | Phone;
interface Laptop {
type: 'Laptop';
countDriveBays: number;
hasTouchScreen: boolean;
}
interface Desktop {
type: 'Desktop';
countDriveBays: number;
}
interface Phone {
type: 'Phone';
hasTouchScreen: boolean;
}
function printInfo(device: Device) {
if (device.type === 'Laptop') {
// device: Laptop
console.log(
`A laptop with ${device.countDriveBays} drive bays and ${
device.hasTouchScreen ? 'a' : 'no'
} touchscreen.`,
);
} else if (device.type === 'Desktop') {
// device: Desktop
console.log(`A desktop with ${device.countDriveBays} drive bays.`);
} else {
// device: Phone
console.log(`A phone with ${device.hasTouchScreen ? 'a' : 'no'} touchscreen.`);
}
}
I want to write a function isDeviceType
in a generic way:
const isDeviceType = <T extends Device['type']>(type: T) => {
return (device: Device): device is DeviceOf<T> => device.type === type;
}
// e.g.
const isPhone = isDeviceType('Phone');
isPhone({ type: 'Phone', hasTouchScreen: true }); // true
However, the way I have defined the DeviceOf
type is pretty verbose since it lists every single type within the union:
type DeviceOf<Type extends Device['type']> =
Type extends Laptop['type'] ? Laptop :
Type extends Desktop['type'] ? Desktop :
Type extends Phone['type'] ? Phone :
never;
Is there a more concise way to define DeviceOf
? I have tried these:
type DeviceOf<Type extends Device['type']> =
(infer D)['type'] extends Type ? D : never;
// TS2536: Type '"type"' cannot be used to index type 'D'.
// TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
// TS6133: 'D' is declared but its value is never read.
type DeviceOf<Type extends Device['type']> =
(infer D) extends Device
? D['type'] extends Type
? D
: never
: never;
// TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
// TS6133: 'D' is declared but its value is never read.
// TS2304: Cannot find name 'D'.
My impression is that error TS1338 is the limiting factor, and so it's impossible to define DeviceOf
in a generic way in the current version of TypeScript.
解决方案
找到了另一种方法,只使用没有infer
关键字的条件类型:
type FindByType<Union, Type> = Union extends { type: Type } ? Union : never;
type DeviceOf<Type extends Device['type']> = FindByType<Device, Type>;
type Result = DeviceOf<'Laptop'>;
基于 Ryan Cavanaugh 的评论:https ://github.com/Microsoft/TypeScript/issues/17915#issuecomment-413347828
推荐阅读
- html - 如何在html中右对齐按钮
- sql-server - 错误消息“消息 102,级别 15,状态 1,第 10 行 '.' 附近的语法不正确”
- javascript - 动态按钮上jquery中所有浏览器上的弹出块
- spring - @RequestBody 在 Spring Boot 中不起作用
- python - Python 中的异常处理,无需大量的异常语句
- salesforce - 如何使用用户名和密码验证用户?
- reactjs - 我可以实时访问我的 heroku 应用程序,但我的朋友只能看到白色的空白页?
- windows-installer - 启用 MSI 安装程序的安装日志,无需任何命令行参数
- amazon-web-services - AWS ECS 上的 NTP 时间同步服务
- css - 使 HTML Unicode (UTF-8) 参考图标变细