typescript - Hämta objekttyp baserat på dess egenskapsvärden
Jag försöker minska mängden kod och fel genom att införa statiska begränsningar för chattkommandon. Kommandon beskrivs med ett namn och argument. Här är vad jag har:
type ArgumentTypeName = 'string' | 'number' | 'user';
type CommandArguments<T> = { [k in keyof T]: ArgumentTypeName };
type CommandHandler<T> = (args: T) => void;
type Command<T> = {
name: string;
arguments: CommandArguments<T>;
handler: CommandHandler<T>;
}
Jag kan inte lista ut hur man får T-typen från arguments av Command<T>, så att det kan korrekt påverka handler. Hittills får jag en typ med rätt nycklar, men alla värden är unknown. Så här:
arguments = { arg1: 'string' }
// handler: (args: { arg1: unknown }) => void
Jag försökte också att begränsa arguments, och tänkte att om jag inte specificerar T, kommer det att härledas från arguments, men det fungerade inte:
type ArgumentName<T> =
T extends string ? 'string' :
T extends number ? 'number' :
T extends User ? 'user' :
never;
type CommandArguments<T> = { [k in keyof T]: ArgumentName<T[k]> };
type CommandHandler<T> = (args: T) => void;
type Command<T> = {
name: string;
arguments: CommandArguments<T>;
handler: CommandHandler<T>;
}
Hur kan jag, typ, härleda bakåt T baserat på dess egenskap CommandArguments<T>?
Så att jag får CommandArguments<T> → T → CommandHandler<T>?
Eller finns det ett annat tillvägagångssätt som jag inte tänkt på?
Redigera: Exempel på kod som använder detta:
/*
In another file:
const commands: Command<any>[] = [];
export function createCommand<T>(cmd: Command<T>) {
commands.push(cmd);
}
*/
createCommand({
name: 'add',
arguments: {
a: 'number',
b: 'number'
},
handler: (args) => {
// (parameter) args: { a: unknown; b: unknown; }
// Error: Object is of type 'unknown'. ts(2571)
console.log(args.a + args.b);
},
});
Svar #1
För att härleda CommandHandler från CommandArguments, vill vi förmodligen inte göra motsatsen, det vill säga ArgumentName<T[k]> i
type CommandArguments<T> = { [k in keyof T]: ArgumentName<T[k]> };
Ett annat problem med ovanstående tillvägagångssätt är att från ovanstående deklaration begränsar CommandArguments endast nycklarna i T, därför härleds T's egenskapers värde-typ till att bara vara unknown.
En enkel lösning är att begränsa arguments's typ T till att utöka ett objekt av ArgumentTypeName, och sedan härleda handler's args från den typen T. Här behöver vi använda T extends CommandArguments istället för CommandArguments för att fånga den faktiska användningstypen.
declare interface User{};
type ArgumentType<T> =
T extends 'string' ? string :
T extends 'number' ? number :
T extends 'user' ? User :
never;
type ArgumentTypeName = 'string' | 'number' | 'user';
type CommandArguments = { [k: string]: ArgumentTypeName };
type CommandHandler<T> = (args: { [k in keyof T]: ArgumentType<T[k]>}) => void;
type Command<T extends CommandArguments> = {
name: string;
arguments: T;
handler: CommandHandler<T>;
}
const commands: Command<any>[] = [];
export function createCommand<T extends CommandArguments>(cmd: Command<T>) {
commands.push(cmd);
}
createCommand({
name: 'add',
arguments: {
a: 'number',
b: 'string',
c: 'user',
},
handler: (args) => {
// (parameter) args: { a: number; b: string; c: User }
console.log(args.a.toPrecision(2) + args.b.padStart(30));
},
});
Lekplatslänk
