Attualmente sto lavorando a un progetto typescript e mi piace molto l’inferenza di tipo che TypeScript porta in tavola. Tuttavia, durante il richiamo di oggetti da chiamate HTTP, posso lanciarli nel tipo desiderato, ottenere il completamento del codice e richiamare le funzioni su di essi per compilare il tempo , ma questi causano errori di runtime
Esempio:
class Person{ name: string; public giveName() { return this.name; } constructor(json: any) { this.name = json.name; } } var somejson = { 'name' : 'John' }; // Typically from AJAX call var john = (somejson); // The cast console.log(john.name); // 'John' console.log(john.giveName()); // 'undefined is not a function'
Anche se questo si compila bene – e intellisense mi suggerisce di usare la funzione, dà un’eccezione di runtime. Una soluzione per questo potrebbe essere:
var somejson = { 'name' : 'Ann' }; var ann = new Person(somejson); console.log(ann.name); // 'Ann' console.log(ann.giveName()); // 'Ann'
Ma questo mi richiederà di creare costruttori per tutti i miei tipi. In particolare, quando si tratta di tipi ad albero e / o di raccolte provenienti dalla chiamata AJAX, è necessario scorrere tutti gli elementi e creare nuovamente un’istanza per ciascuno.
Quindi la mia domanda: c’è un modo più elegante per farlo? Cioè, gettare su un tipo e avere le funzioni prototipo disponibili per esso immediatamente?
Dai un’occhiata al JavaScript compilato e vedrai che il tipo asserzione (casting) scompare perché è solo per la compilazione. In questo momento stai dicendo al compilatore che l’object somejson
è di tipo Person
. Il compilatore ti crede, ma in questo caso non è vero.
Quindi questo problema è un problema di JavaScript in fase di esecuzione.
L’objective principale per farlo funzionare è in qualche modo dire a JavaScript quale sia la relazione tra le classi. Così…
Ci sono molti modi per risolverlo, ma offrirò un esempio in cima alla mia testa. Questo dovrebbe aiutare a descrivere ciò che deve essere fatto.
Diciamo che abbiamo questa class:
class Person { name: string; child: Person; public giveName() { return this.name; } }
E questi dati JSON:
{ name: 'John', child: { name: 'Sarah', child: { name: 'Jacob' } } }
Per mappare questo automaticamente come istanze di Person
, dobbiamo dire al JavaScript come sono correlati i tipi. Non possiamo usare le informazioni sul tipo TypeScript perché lo perderemo una volta compilato. Un modo per farlo è avere una proprietà statica sul tipo che descrive questo. Per esempio:
class Person { static relationships = { child: Person }; name: string; child: Person; public giveName() { return this.name; } }
Quindi ecco un esempio di una funzione riutilizzabile che gestisce la creazione degli oggetti per noi sulla base di questi dati di relazione:
function createInstanceFromJson(objType: { new(): T; }, json: any) { const newObj = new objType(); const relationships = objType["relationships"] || {}; for (const prop in json) { if (json.hasOwnProperty(prop)) { if (newObj[prop] == null) { if (relationships[prop] == null) { newObj[prop] = json[prop]; } else { newObj[prop] = createInstanceFromJson(relationships[prop], json[prop]); } } else { console.warn(`Property ${prop} not set because it already existed on the object.`); } } } return newObj; }
Ora il seguente codice funzionerà:
const someJson = { name: 'John', child: { name: 'Sarah', child: { name: 'Jacob' } } }; const person = createInstanceFromJson(Person, someJson); console.log(person.giveName()); // John console.log(person.child.giveName()); // Sarah console.log(person.child.child.giveName()); // Jacob
Terreno di gioco
Idealmente , il modo migliore sarebbe utilizzare qualcosa che in realtà legge il codice TypeScript e crea un object che mantiene la relazione tra le classi. In questo modo non è necessario mantenere manualmente le relazioni e preoccuparsi delle modifiche al codice. Ad esempio, in questo momento il codice di refactoring è un po ‘un rischio con questa configurazione. Non sono sicuro che esista qualcosa del genere al momento, ma è sicuramente ansible.
Soluzione alternativa
Mi sono appena reso conto che ho già risposto a una domanda simile con una soluzione leggermente diversa (che non coinvolge però i dati nidificati). Puoi leggerlo qui per qualche altra idea:
Istanza di class JSON to TypeScript?
Il prototipo della class può essere influenzato in modo dinamico sull’object:
function cast(obj: any, cl: { new(...args): T }): T { obj.__proto__ = cl.prototype; return obj; } var john = cast(/* somejson */, Person);
Vedi la documentazione di __proto__
qui .