ค้นหาเว็บไซต์

typescript - การจำแนกประเภทเฉพาะสำหรับอาร์เรย์ที่มีตัวแยกแยะ


ถ้าฉันมีชนิดแบบ string[] | number[] มีวิธีจำกัดชนิดให้เป็นหนึ่งในชนิด array เหล่านี้โดยไม่ต้องใช้ฟังก์ชันชัดเจนที่คืนค่าแบบ T is number[] ไหม?

ฉันลองทำสิ่งนี้แล้ว แต่ TypeScript (เวอร์ชันล่าสุดตอนนี้คือ 5.5.4) ไม่เข้าใจ: playground

declare const arr : string[] | number[]

if (arr.length) {
  if (typeof arr[0] === 'string') {
    // Property 'substr' does not exist on type 'string | number'.
    //   Property 'substr' does not exist on type 'number'
    arr.map(x => x.substr(1))
  } else {
    // Operator '+' cannot be applied to types 'string | number' and 'number'.
    arr.map(x => x + 1)
  }
}

คำตอบ #1

TypeScript โดยทั่วไปจะไม่ทำการจำกัดประเภทของอ็อบเจ็กต์เมื่อคุณจำกัดเพียงหนึ่งในคุณสมบัติของมัน มีคำขอฟีเจอร์เปิดสำหรับพฤติกรรมนี้ที่ microsoft/TypeScript#42384 แต่ตอนนี้มันยังไม่ใช่ส่วนหนึ่งของภาษา วิธีเดียวที่คุณสามารถจำกัดประเภทของอ็อบเจ็กต์โดยการตรวจสอบคุณสมบัติคือถ้าประเภทของอ็อบเจ็กต์เป็น discriminated union และคุณตรวจสอบคุณสมบัติ discriminant แต่ในขณะที่ string[] | number[] เป็น union มันไม่ใช่ discriminated union คุณสมบัติ discriminant ต้องเป็นประเภท literal เช่น "abc" หรือ 123 หรือ true (หรืออย่างน้อยหนึ่งในองค์ประกอบของ union ต้องมีประเภท literal ดังกล่าว) ทั้ง string และ number ไม่ใช่ประเภท literal ดังนั้นการตรวจสอบ typeof arr[0] === "string" จะไม่ทำให้ arr ถูกจำกัดประเภท


ตอนนี้ถ้าคุณต้องการให้เกิดการจำกัดประเภท คุณจะต้องเขียนฟังก์ชันตัวกรองประเภท วิธีหนึ่งในการทำสิ่งนี้คือเลียนแบบพฤติกรรมที่ต้องการโดยทั่วไปของการจำกัดประเภทของวัตถุโดยการตรวจสอบคุณสมบัติ:

function unionPropGuard<T, K extends keyof T, U extends T[K]>(
  obj: T, key: K, guard: (x: T[K]) => x is U):
  obj is T extends unknown ? (T[K] & U) extends never ? never : T : never {
  return guard(obj[key])
}

นี่จะดึงทุกองค์ประกอบของยูเนียน T ที่คุณสมบัติ K มีความทับซ้อนกับชนิดที่ถูกป้องกัน U ตอนนี้คุณสามารถเขียนใหม่ if (guard(obj[prop])) เป็น if (unionPropGuard(obj, prop, guard)) ในตัวอย่างโค้ดของคุณจะมีลักษณะดังนี้:

if (arr.length) {
  if (unionPropGuard(arr, 0, x => typeof x === "string")) {
    arr.map(x => x.substring(1))
  } else {
    arr.map(x => x + 1)
  }
}

โปรดทราบว่า x => typeof x === "string" ถูกอนุมานให้เป็นฟังก์ชันตัวคุมชนิดของประเภท (x: string | number) => x is string ดังนั้นการเรียก unionPropGuard() จะทำการจำกัดประเภทซึ่งคุณพยายามทำโดยการเขียน typeof arr[0] === "string" หากมันคืนค่า true ก็จะทำให้ arr ถูกจำกัดเป็น string[] มิฉะนั้น arr จะถูกจำกัดเป็น number[].

ลิงก์ Playground ไปยังโค้ด

คำตอบ #2

แล้วถ้าเราบอก TypeScript อย่างชัดเจนว่าประเภทคืออะไรหลังจากการตรวจสอบล่ะ?

การใช้การยืนยันประเภทเช่น

declare const arr : string[] | number[];

if (arr.length) {
  if (typeof arr[0] === 'string') {
    const stringArr = arr as string[];
    stringArr.map(x => x.substr(1));
  } else {
    const numberArr = arr as number[];
    numberArr.map(x => x + 1)
  }
}