typescript - Accesarea sub-obiectelor unui obiect fără a folosi any
Sunt în procesul de aplicare a unui TypeScript mai bun într-un proiect. Există un obiect complex venind de la backend-ul meu, care este o clasă cu zeci de atribute, majoritatea fiind clase proprii. Avem un formular cu un handler de schimbare comun care în prezent are câteva any-uri pentru a funcționa, dar mi-ar plăcea să rezolv asta folosind generice.
Aceasta este o versiune simplificată a modelului din aplicația mea, dar sunt capabil să reproduc aceleași erori pe care le văd:
class PhoneNumber {
prefix: number;
exchange: number;
line: number;
}
class Address {
address1: string;
address2: string;
city: string;
}
class User {
phone: PhoneNumber;
address: Address;
}
const user = new User();
function setValue<A extends keyof User, T extends User[A], K extends keyof T, V extends T[K]>(
value: V,
fieldType: new () => T,
fieldName: A,
fieldAttribute: K
) {
if (!user[fieldName]) {
user[fieldName] = new fieldType();
}
const field = user[fieldName];
// error here.
field[fieldAttribute] = value;
}
setValue(408, PhoneNumber, 'phone', 'prefix');
Am încercat câteva lucruri și asta este cât de aproape am reușit să ajung de o soluție. Pot să inspectez apelul funcției la final în IDE-ul meu și se pare că genericele sunt completate cu ceva rezonabil: function setValue<"phone", PhoneNumber, "prefix", number>(), dar apoi primesc o eroare în timpul compilării:
TS2536: Tipul 'K' nu poate fi folosit pentru a indexa tipul 'User[A]'.
Mă abordez greșit această problemă? Dacă nu reușesc să înțeleg asta, o să ajung să împart pur și simplu diferitele tipuri în gestionatori separați.
Există, de asemenea, un caz în care unele dintre câmpuri sunt tablouri de obiecte, ceea ce adaugă o altă complicație întregii situații.
Răspuns #1
Cu tipurile de argumente actuale ale setValue, s-ar putea furniza o clasă child B ca fieldType (care extinde clasa A user[fieldName]), și să se țintească un fieldAttribute al lui B, care s-ar putea să nu existe în A:
class PhoneNumber2 extends PhoneNumber {
extension: number;
}
// Complies with setValue types,
// but if `user` already has a basic PhoneNumber instance,
// the code within setValue fails
setValue(408, PhoneNumber2, "phone", "extension");
// within setValue:
user["phone"]["extension"] = 408;
// ...but user["phone"] may be a PhoneNumber,
// not necessarily a PhoneNumber2!
În cazul tău, în loc de K extends keyof T (T fiind tipul de întoarcere al fieldType, posibil o clasă copil), ai putea folosi doar K extends keyof User[A], pentru a te asigura că fieldAttribute vizează clasa de bază:
function setValue2<A extends keyof User, T extends User[A], K extends keyof User[A], V extends T[K]>(
value: V,
fieldType: new () => T,
fieldName: A,
fieldAttribute: K
) {
if (!user[fieldName]) {
user[fieldName] = new fieldType();
}
const field = user[fieldName];
field[fieldAttribute] = value; // Now okay
}
setValue2(408, PhoneNumber, 'phone', 'prefix');
setValue2(408, PhoneNumber2, 'phone', 'prefix');
setValue2(408, PhoneNumber2, 'phone', 'extension'); // Error: Argument of type '"extension"' is not assignable to parameter of type 'keyof PhoneNumber'.
Ai putea chiar să scapi de tipul generic T:
function setValue3<A extends keyof User, K extends keyof User[A], V extends User[A][K]>(
value: V,
fieldType: new () => User[A],
fieldName: A,
fieldAttribute: K
) {}
Link către terenul de joacă
