2012-10-07 5 views
39

Desidero creare un oggetto funzione, che contiene anche alcune proprietà su di esso. Per esempio in JavaScript che vorrei fare:Creare un oggetto funzione con proprietà in TypeScript

var f = function() { } 
f.someValue = 3; 

Ora a macchina posso descrivere il tipo di questo come:

var f: {(): any; someValue: number; }; 

Tuttavia non posso davvero costruirlo, senza la necessità di un cast. Ad esempio:

var f: {(): any; someValue: number; } = 
    <{(): any; someValue: number; }>(
     function() { } 
    ); 
f.someValue = 3; 

Come si costruisce questo senza un cast?

+1

Questa non è una risposta diretta alla sua domanda, ma per chi vuole per creare in modo conciso un oggetto funzione con proprietà, ed è OK con il cast, l'operatore [Object-Spread] (https://blog.mariusschulz.com/2016/12/23/typescript-2-1-object-rest-and -spread) sembra fare il trucco: 'var f: {(): any; someValue: numero; } = <{(): any; someValue: numero; }> { ... (() => "Ciao"), someValue: 3 }; '. – Jonathan

risposta

14

Quindi, se il requisito è di costruire semplicemente e assegnare tale funzione "f" senza un cast, ecco una possibile soluzione:

var f: {(): any; someValue: number; }; 

f = (() => { 
    var _f : any = function() { }; 
    _f.someValue = 3; 
    return _f; 
})(); 

Essenzialmente, si utilizza una funzione di esecuzione sé letterale "costruire" un oggetto che corrisponderà a tale firma prima che l'assegnazione venga eseguita. L'unica stranezza è che la dichiarazione interna della funzione deve essere di tipo 'any', altrimenti il ​​compilatore grida che stai assegnando a una proprietà che non esiste ancora sull'oggetto.

MODIFICA: Semplificato il codice un po '.

+2

Per quanto posso capire, in realtà questo non controllerà il tipo, quindi .someValue potrebbe essere essenzialmente qualsiasi cosa. – shabunc

1

Come scorciatoia, è possibile assegnare dinamicamente il valore oggetto utilizzando la funzione di accesso [ 'proprietà']:

var f = function() { } 
f['someValue'] = 3; 

Questo bypassa il controllo dei tipi. Tuttavia, è abbastanza sicuro perché si deve accedere intenzionalmente la proprietà allo stesso modo:

var val = f.someValue; // This won't work 
var val = f['someValue']; // Yeah, I meant to do that 

Tuttavia, se si vuole veramente il tipo di controllo per il valore della proprietà, questo non funzionerà.

68

Aggiornamento: questa risposta è stata la soluzione migliore nelle versioni precedenti di TypeScript, ma sono disponibili opzioni migliori nelle versioni più recenti (vedere altre risposte).

La risposta accettata funziona e potrebbe essere necessaria in alcune situazioni, ma ha lo svantaggio di non fornire alcuna sicurezza di tipo per la creazione dell'oggetto. Questa tecnica genererà almeno un errore di tipo se si tenta di aggiungere una proprietà non definita.

interface F {(): any; someValue: number; } 

var f = <F>function() { } 
f.someValue = 3 

// type error 
f.notDeclard = 3 
+3

questo è fantastico, anche questo – peter

+1

è più facile da leggere –

+1

Non capisco perché la riga 'var f' non causi un errore, poiché in quel momento non esiste la proprietà' someValue'. –

0

Questa parte dalla tipizzazione forte, ma si può fare

var f: any = function() { } 
f.someValue = 3; 

se si sta cercando di aggirare opprimente tipizzazione forte come mi è stato quando ho trovato questa domanda. Purtroppo questo è un caso che TypeScript fallisce su JavaScript perfettamente valido, quindi devi dire a TypeScript di fare marcia indietro.

"JavaScript è perfettamente valido, TypeScript" viene valutato come falso. (Nota: utilizzando 0.95)

28

dattiloscritto è stato progettato per gestire questo caso attraverso declaration merging:

you may also be familiar with JavaScript practice of creating a function and then extending the function further by adding properties onto the function. TypeScript uses declaration merging to build up definitions like this in a type-safe way.

Dichiarazione fusione ci permette di dire che qualcosa è al tempo stesso una funzione e uno spazio dei nomi (modulo interno):

function f() { } 
namespace f { 
    export var someValue = 3; 
} 

Questo preserva la digitazione e ci consente di scrivere sia f() e f.someValue. Quando si scrive un file .d.ts per il codice JavaScript esistente, utilizzare declare:

declare function f(): void; 
declare namespace f { 
    export var someValue: number; 
} 

Aggiunta di proprietà alle funzioni è spesso un motivo di confusione o imprevisti a macchina, in modo da cercare di evitarlo, ma può essere necessario quando si utilizza o la conversione di anziani Codice JS. Questa è una delle uniche volte in cui è opportuno combinare moduli interni (namespace) con esterni.

+0

per menzionare i moduli ambientali nella risposta. Questo è un caso tipico durante la conversione o l'annotazione di moduli JS esistenti. Esattamente quello che sto cercando! – Nipheris

+0

Bello. Se vuoi usare questo con i moduli ES6, puoi semplicemente fare '' 'function hello() {..}' '' '' 'namespace ciao { export const value = 5; } '' ' ' '' export default hello; '' ' IMO questo è molto più pulito di Object.assign o di simili hack di runtime. Nessun runtime, nessun tipo di affermazione, niente di niente. – skrebbel

+0

Questa risposta è ottima, ma come si allega un simbolo come Symbol.iterator a una funzione? – kzh

1

Una risposta aggiornata: poiché l'aggiunta di tipi di intersezione tramite &, è possibile "unire" due tipi dedotti al volo.

Ecco un helper generale che legge le proprietà di alcuni oggetti from e li copia su un oggetto onto. Restituisce lo stesso oggetto onto ma con un nuovo tipo che comprende due serie di proprietà, così correttamente descrivere il comportamento di compressione:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 { 
    Object.keys(from).forEach(key => onto[key] = from[key]); 
    return onto as T1 & T2; 
} 

Questo helper basso livello non ancora eseguire un tipo affermazione, ma è del tipo sicuro dal design. Con questo helper in atto, abbiamo un operatore che possiamo utilizzare per risolvere il problema del PO, con la sicurezza di tipo integrale:

interface Foo { 
    (message: string): void; 
    bar(count: number): void; 
} 

const foo: Foo = merge(
    (message: string) => console.log(`message is ${message}`), { 
     bar(count: number) { 
      console.log(`bar was passed ${count}`) 
     } 
    } 
); 

Click here to try it out in the TypeScript Playground. Si noti che abbiamo limitato foo per essere di tipo Foo, quindi il risultato di merge deve essere uno Foo completo. Quindi se si rinomina bar in bad si ottiene un errore di tipo.

NB C'è ancora un tipo di foro qui, tuttavia. TypeScript non fornisce un modo per vincolare un parametro di tipo "non una funzione". Quindi potresti confondermi e passare la tua funzione come secondo argomento a merge, e questo non funzionerebbe. Così fino a quando questo può essere dichiarato, dobbiamo prenderlo in fase di esecuzione:

function merge<T1, T2>(onto: T1, from: T2): T1 & T2 { 
    if (typeof from !== "object" || from instanceof Array) { 
     throw new Error("merge: 'from' must be an ordinary object"); 
    } 
    Object.keys(from).forEach(key => onto[key] = from[key]); 
    return onto as T1 & T2; 
} 
21

Questo è facilmente realizzabile ora (dattiloscritto 2.x) con Object.assign(target, source)

esempio:

enter image description here

La magia qui è che Object.assign<T, U>(...) viene digitato per restituire un unione tipo di T & U.

Applicazione che questo si risolve in un'interfaccia nota è anche diretta:

interface Foo { 
    (a: number, b: string): string[]; 
    foo: string; 
} 

let method: Foo = Object.assign(
    (a: number, b: string) => { return a * a; }, 
    { foo: 10 } 
); 

Error: foo:number not assignable to foo:string
Error: number not assignable to string[] (return type)

avvertimento: potrebbe essere necessario polyfill Object.assign se destinati a un browser più vecchi.

+0

Fantastico, grazie. A partire da febbraio 2017, questa è la migliore risposta (per gli utenti di Typescript 2.x). –