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

javascript - Typescript union กับประเภทที่ขึ้นต่อกัน


ถ้าฉันมียูเนียนดังต่อไปนี้ที่ห่อหุ้มฟังก์ชันและอาร์กิวเมนต์ของมัน ฉันจะเรียกใช้มันได้อย่างไร?

type Wrapper = {
  fn: (a: string) => void
  arg: string
} | {
  fn: (a: number) => void
  arg: number
}

let foo!: Wrapper
foo.fn(foo.arg) // Error Type 'string' is not assignable to type 'never'.

ฉันไม่สามารถหาวิธีเรียกมันได้ ทุกอย่างที่ฉันลองโดยพื้นฐานแล้วลงเอยที่การคาสต์ (เช่น การคาสต์เป็น (a:any) => void) ซึ่งฉันสามารถทำได้ถ้าจำเป็น แต่ฉันรู้สึกว่าฉันไม่ควรต้องทำแบบนั้น

สามารถเรียกฟังก์ชันนี้โดยไม่ต้องแปลงประเภทได้หรือไม่?

แก้ไข: เพื่อชี้แจง ฉันกำลังถามว่ามีวิธีแก้ปัญหาที่ไม่เกี่ยวข้องกับการเปลี่ยนแปลงคำนิยามของ Wrapper หรือไม่

คำตอบ #1

คุณสามารถใช้ชนิดแบบมีเงื่อนไขเพื่อทำให้ประเภทไม่กำกวม น่าเสียดายที่มันทำให้คุณต้องทำซ้ำประเภทยูเนียนของตัวห่ออีกครั้ง และถ้าคุณมีสิ่งที่ต้องตรวจสอบมากขึ้น ชนิดแบบมีเงื่อนไขของคุณจะดูน่าเกลียด แต่ก็ทำให้คุณหลีกเลี่ยงการแปลงประเภท (ในบางแง่) ได้

type Wrapper = {
  fn: (a: string) => void
  arg: string
} | {
  fn: (a: number) => void
  arg: number
}

type Disambiguate<W extends Wrapper> = W['arg'] extends string
  ? { fn: (a : string) => void, arg: string }
  : { fn: (a: number) => void, arg: number };

let foo!: Disambiguate<Wrapper>

// The compiler is satisfied. No error here.
// foo can only be one of the branches in the conditional type.
foo.fn(foo.arg);

คำตอบ #2

ไม่แน่ใจนักเกี่ยวกับกรณีการใช้งานของคุณ แต่สิ่งด้านบนสามารถทำได้โดยใช้สิ่งนี้:

type ArgType = number | string

type Wrapper<T extends ArgType> = {
  fn: (a: T) => void
  arg: T
}

let foo!: Wrapper<number>
foo.fn(foo.arg)

let foo1!: Wrapper<string>
foo1.fn(foo1.arg)