簡體   English   中英

TypeScript:根據字符串文字屬性一般推斷聯合類型成員

[英]TypeScript: generically infer union type member based on a string literal property

TypeScript (v3.2.2) 允許我定義接口的聯合,每個接口都有一個唯一的字符串文字屬性,可以用作類型保護,例如

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.`);
  }
}

我想以通用方式編寫 function isDeviceType

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

但是,我定義DeviceOf類型的方式非常冗長,因為它列出了聯合中的每一個類型:

type DeviceOf<Type extends Device['type']> =
  Type extends Laptop['type'] ? Laptop :
  Type extends Desktop['type'] ? Desktop :
  Type extends Phone['type'] ? Phone :
  never;

有沒有更簡潔的方法來定義DeviceOf 我試過這些:

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'.

我的印象是錯誤 TS1338 是限制因素,因此不可能在當前版本的 TypeScript 中以通用方式定義DeviceOf

知道了。 您必須應用“if”兩次,一次用於創建infer類型,第二次用於檢查infer類型是否擴展設備。 只有在分支D extends Device你才能使用D['type']

type DeviceOf<Type extends Device['type']> =
  Device extends (infer D) ?
  D extends Device ?
  D['type'] extends Type ? D : never : never : never;

type Result = DeviceOf<'Laptop'>;

操場

找到了另一種方法,只使用沒有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

Typescript 2.8 支持Extract<Type, Union>實用程序,這使得實現起來更加簡單。

type Result = Extract<Device, { type: 'Laptop' }>;

來自此處的 Typescript 文檔:

通過從Type中提取可分配給Union的所有聯合成員來構造一個類型。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM