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

Typescript สามารถประกาศชนิดทั่วไปสำหรับอาเรย์ของสตริงจำนวน 1-N ได้หรือไม่


ค่อนข้างยากที่จะอธิบายในชื่อเรื่อง แต่โดยพื้นฐานแล้วฉันอยากจะสามารถประกาศอาร์เรย์ชนิดสตริงที่มีความยาวคงที่ตั้งแต่ 1 ถึง N

interface Command {
  [key: string]: (args: [string, ...string[]]) => boolean;
}

const cmd: Command = {
  TEST: (args: [string, string]) => args[0] === args[1],
  TEST2: (args: [string]) => args[0] === 'hello'
}

ดังนั้นสิ่งนี้จึงใช้ไม่ได้ เนื่องจาก [string, string] แตกต่างจาก string[]:

Type '(args: [string]) => boolean' is not assignable to type '(args: [string, ...string[]]) => boolean'.

วิธีแก้ปัญหาอาจเป็นการกำหนดเหมือนกับทุกประเภทของอาร์กิวเมนต์:

interface Command {
  [key: string]: (args: [string] | [string, string] | [string, string, string]) => boolean;
}

แต่มันยาวไปหน่อยสำหรับสิ่งที่เรียบง่าย (เห็นด้วยว่ามันสามารถห่อหุ้มในอินเทอร์เฟซได้) อย่างไรก็ตามมีวิธีแก้ปัญหาที่สวยงามอีกวิธีหนึ่งที่ฉันไม่เห็นหรือเปล่า?

ขอบคุณ,

คำตอบ #1

ฉันสงสัยว่าคุณอาจต้องการให้ Command เป็นแบบ generic เพื่อให้คุณสมบัติของมันสามารถใช้งานได้จริง ถ้าคุณมีชนิดฟังก์ชันแบบ (arg: [string] | [string, string]) => boolean คุณจะไม่สามารถใช้งานมันด้วย (arg: [string]) => boolean หรือ (arg: [string, string]) => boolean ได้ ชนิดของฟังก์ชันจะเปลี่ยนแปลงอย่าง contravariant ในชนิดของอาร์กิวเมนต์ คุณสามารถขยายได้ แต่ไม่สามารถทำให้แคบลงในชนิดของอาร์กิวเมนต์ฟังก์ชันได้ เว้นแต่คุณต้องการให้ทุกวิธีของชนิด Command ต้องรับอาร์เรย์ของสตริงทุกประเภทที่มีความยาวหนึ่งหรือมากกว่า คุณจะต้องทำบางอย่างกับ generics

นี่คือตัวอย่างชนิดที่เป็นไปได้สำหรับ Command พร้อมกับฟังก์ชันช่วยเหลือ asCommand() ซึ่งช่วยให้คอมไพเลอร์สามารถสรุปค่าที่ถูกต้องของ T จากค่าชนิด Command<T> โดยไม่ต้องบังคับให้คุณเขียนมันด้วยตัวเอง:

type Command<T> = { [K in keyof T]: (args: T[K]) => boolean }

const asCommand = <T extends Record<keyof T, [string, ...string[]]>>(
    cmd: Command<T>
) => cmd;

จากนั้น ค่าคงที่ cmd ของคุณสามารถประกาศได้แบบนี้โดยไม่มีข้อผิดพลาด:

const cmd = asCommand({
    TEST: (args: [string, string]) => args[0] === args[1],
    TEST2: (args: [string]) => args[0] === 'hello'
});

และคอมไพเลอร์จะจำได้ว่า cmd.TEST รับคู่ค่าและ cmd.TEST2 รับทูเพิลหนึ่งค่า:

cmd.TEST(["", ""]); // okay
cmd.TEST([]); // error
cmd.TEST([""]); // error
cmd.TEST(["", "", ""]); // error

cmd.TEST2([""]); // okay
cmd.TEST2([]); // error
cmd.TEST2(["", ""]); // error
cmd.TEST2(["", "", ""]); // error

และคุณไม่สามารถให้สมบัติที่รับเฉพาะทูเพิลความยาวศูนย์ได้:

const badCmd = asCommand({
    OOPS: (args: []) => false, // error!
})

ฉันหวังว่านั่นจะช่วยให้คุณมีทิศทางบ้าง โชคดีนะ!

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