Înlocuiește mai multe șiruri specifice în tipul TypeScript
Caut o modalitate de a înlocui cheile unui tip atunci când conține cheia altuia. Fiecare cheie va conține doar o singură înlocuire (în exemplu, 'abc' va exista doar o dată într-o cheie) care va trebui mapată la valoarea sa echivalentă în tipul de înlocuire.
În prezent fac:
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>;
Ce îmi dă acest tip așa cum era de așteptat:
type Replaced = {
dcb: string;
aabca: string;
}
Totuși, mă întreb dacă există o modalitate de a înlocui cheile care sunt conținute. Am încercat să creez un tip pentru a face această treabă:
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>;
Totuși, tipul returnat nu conține nicio înlocuire:
type ReplacedInferred = {
bcd: string;
aabca: string;
}
Nu sunt sigur unde greșesc.
Editare: Am încercat, de asemenea:
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];
};
Care este, practic, un pas în plus față de primul tip. Aceasta funcționează la fel ca primul tip, oferind înapoi:
type ReplacedInferred = {
bcd: string;
aabca: string;
}
Care tot nu înlocuiește niciunul dintre șirurile conținute.
Răspuns #1
Abordarea generală ar fi
type ReplaceKeysInferred<R extends { [k: string]: string }, T extends object> = {
[K in keyof T as K extends string ? Substitute<R, K> : K]: T[K]
}
unde Substitute<R, K> înlocuiește orice intrare corespunzătoare din R în interiorul șirului K. O modalitate de a implementa Substitute este:
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;
Cartografiem peste cheile de tip șir ale R și încercăm să potrivim fiecare astfel de cheie K cu un subșir al T. Acest lucru folosește inferența literalelor de șablon de forma T extends `$ {infer F}$ {K}$ {infer L}` ? `$ {F}$ {R[K]}$ {L}` : never. Așadar, dacă K apare în interiorul T, este înlocuit cu R[K]. În caz contrar, obținem never.
Motivul pentru care funcționează și K extends `$ {infer A}$ {infer RKey}$ {infer B}` nu funcționează este că A, RKey și B sunt toate parametri generici temporari. Și conform descrierii din microsoft/TypeScript#40336, PR-ul care a implementat tipurile de literal de șablon, „un temporar urmat imediat de alt temporar este asociat prin inferarea unui singur caracter din sursă.” Aceasta înseamnă că atât A cât și RKey vor avea cel mult un caracter. În schimb, în `$ {infer F}$ {K}$ {infer L}`, K nu este un temporar.
Oricum, apoi indexăm în tipul mapat cu string & keyof R, rezultând în uniunea substituțiilor pentru fiecare cheie K din R. Ne așteptăm ca să existe cel mult o astfel de substituție, astfel că uniunea va avea fie un singur membru, fie va fi never.
Tipul de utilitate OrDefault<T, U> se ocupă de never. Dacă substituția există, atunci o returnăm. Altfel, returnăm cheia originală K. În acest fel, o substituție eșuată nu elimină cheia din rezultat.
Bine, să-l testăm:
type Replacements = {
abc: 'cba';
bcd: 'dcb';
};
type Test = {
bcd: string;
aabca: string;
};
type ReplacedInferred = ReplaceKeysInferred<Replacements, Test>;
/* type ReplacedInferred = {
dcb: string;
acbaa: string;
} */
Arată bine. Rețineți că implementarea de mai sus a Substitute depinde puternic de cazul de utilizare menționat în întrebare. Cititorii viitori cu cerințe diferite, cum ar fi potriviri multiple sau suprapuse, ar trebui să schimbe abordarea în consecință.
Link de teren de joacă către cod
