แทนที่สตริงหลายตัวที่เฉพาะเจาะจงในประเภทของ TypeScript
ฉันกำลังมองหาวิธีการแทนที่คีย์ของประเภทหนึ่งเมื่อมันมีคีย์ของอีกประเภทหนึ่ง แต่ละคีย์จะมีการแทนที่เพียงครั้งเดียว (ในตัวอย่าง 'abc' จะปรากฏในคีย์เพียงครั้งเดียว) ซึ่งจะต้องถูกแมปไปยังค่าที่เทียบเท่าในประเภทการแทนที่
ปัจจุบันฉันกำลังทำ:
type Replacements = {
abc: 'cba';
bcd: 'dcb';
};
type Test = {
bcd: string;
aabca: string;
};
type ReplaceKeys<R extends Record<string, string>, T extends Record<string, unknown>> = {
[K in keyof T as K extends keyof R ? R[K] : K]: T[K];
};
type Replaced = ReplaceKeys<Replacements, Test>;
ซึ่งให้ประเภทนี้กับฉันตามที่คาดไว้:
type Replaced = {
dcb: string;
aabca: string;
}
อย่างไรก็ตามฉันสงสัยว่ามีวิธีใดบ้างในการแทนที่คีย์ที่ถูกเก็บไว้ ฉันได้ลองสร้างประเภทเพื่อทำงานนี้:
type ReplaceKeysInferred<
R extends Record<string, string>,
T extends Record<string, unknown>,
> = {
[K in keyof T as K extends `${infer A}${infer RKey extends keyof R & string}${infer B}`
? `${A}${R[RKey]}${B}`
: K]: T[K];
};
type ReplacedInferred = ReplaceKeysInferred<Replacements, Test>;
อย่างไรก็ตาม ประเภทที่ส่งคืนไม่ได้มีการแทนที่ใด ๆ:
type ReplacedInferred = {
bcd: string;
aabca: string;
}
ไม่แน่ใจว่าฉันทำผิดตรงไหน
แก้ไข: ฉันยังได้ลอง:
type ReplaceKeysInferred<
R extends Record<string, string>,
T extends Record<string, unknown>,
> = {
[K in keyof T as K extends `${infer A}${infer RKey}${infer B}`
? RKey extends keyof R
? `${A}${R[RKey]}${B}`
: K
: K]: T[K];
};
ซึ่งโดยพื้นฐานแล้วเป็นขั้นตอนเพิ่มเติมจากประเภทแรก วิธีนี้ทำงานเหมือนกับประเภทแรกโดยให้ผลลัพธ์กลับ:
type ReplacedInferred = {
bcd: string;
aabca: string;
}
ซึ่งยังไม่ได้แทนที่สตริงที่อยู่ภายในใด ๆ
คำตอบ #1
แนวทางทั่วไปจะเป็น
type ReplaceKeysInferred<R extends { [k: string]: string }, T extends object> = {
[K in keyof T as K extends string ? Substitute<R, K> : K]: T[K]
}
ที่ Substitute<R, K> แทนที่รายการที่ตรงกันใด ๆ จาก R ภายในสตริง K วิธีหนึ่งในการใช้งาน Substitute คือ:
type Substitute<R extends { [k: string]: string }, T extends string> =
OrDefault<{ [K in string & keyof R]:
T extends `${infer F}${K}${infer L}` ? `${F}${R[K]}${L}` : never
}[string & keyof R], T>
type OrDefault<T, U> = [T] extends [never] ? U : T;
เรากำลังทำการแมปผ่านคีย์แบบสตริงของ R และพยายามจับคู่แต่ละคีย์นั้น K กับซับสตริงบางส่วนของ T ซึ่งจะใช้การอนุมานเทมเพลตลิเทอรัลในรูปแบบ T extends `$ {infer F}$ {K}$ {infer L}` ? `$ {F}$ {R[K]}$ {L}` : never ดังนั้นถ้า K ปรากฏอยู่ภายใน T มันจะถูกแทนที่ด้วย R[K] มิฉะนั้นเราจะได้ never.
เหตุผลที่วิธีนี้ใช้ได้และ K extends `$ {infer A}$ {infer RKey}$ {infer B}` ใช้ไม่ได้ เพราะ A, RKey และ B เป็นพารามิเตอร์ประเภททั่วไป ตัวแทน ทั้งหมด และตามคำอธิบายใน microsoft/TypeScript#40336 การ PR ที่ใช้ template literal types ระบุว่า “ตัวแทนที่ตามด้วยตัวแทนอีกตัวหนึ่งทันที จะถูกจับคู่โดยการสรุปตัวอักษรเดียวจากแหล่งที่มา” ซึ่งหมายความว่า A และ RKey จะยาวได้ไม่เกินหนึ่งตัวอักษรเท่านั้น ในขณะที่ใน `$ {infer F}$ {K}$ {infer L}`, K ไม่ใช่ตัวแทน
อย่างไรก็ตาม จากนั้นเราจะทำการเข้าดัชนีในประเภทที่แมปด้วย string & keyof R ซึ่งส่งผลให้เกิดสหสัมพันธ์ของการแทนที่สำหรับแต่ละคีย์ K ใน R เราคาดว่าการแทนที่จะมีเพียงตัวเดียวเท่านั้น ดังนั้นสหสัมพันธ์จึงอาจมีสมาชิกเพียงหนึ่งตัว หรืออาจเป็น never
ยูทิลิตี้ไทป์ OrDefault<T, U> ดูแล never หากมีการแทนที่ เราก็จะส่งคืนมัน มิฉะนั้นเราจะส่งคืนคีย์เดิม K ด้วยวิธีนี้ การแทนที่ที่ล้มเหลวจะไม่ลบคีย์ออกจากผลลัพธ์
ตกลง ลองทดสอบมันกันเถอะ:
type Replacements = {
abc: 'cba';
bcd: 'dcb';
};
type Test = {
bcd: string;
aabca: string;
};
type ReplacedInferred = ReplaceKeysInferred<Replacements, Test>;
/* type ReplacedInferred = {
dcb: string;
acbaa: string;
} */
ดูดี หมายเหตุว่าการใช้งาน Substitute ข้างต้นขึ้นอยู่กับกรณีการใช้งานที่ระบุในคำถามเป็นอย่างมาก ผู้อ่านในอนาคตที่มีความต้องการต่างออกไป เช่น การจับคู่หลายรายการหรือการจับคู่ที่ทับซ้อนกัน จะต้องปรับเปลี่ยนวิธีการตามความเหมาะสม
ลิงก์ Playground ไปยังโค้ด
