Ottenere il tipo di ritorno di una funzione


101

Ho la seguente funzione:

function test(): number {
    return 42;
}

Posso ottenere il tipo di funzione utilizzando typeof:

type t = typeof test;

Qui tsarà () => number.

C'è un modo per ottenere il tipo di ritorno della funzione? Vorrei tessere numberinvece di () => number.


2
No, comunque non con il tipo. typeof restituirà solo "function" (il mio case potrebbe essere sbagliato ).
Igor

Sarebbe fantastico se questo diventasse possibile. A volte definisci i tipi mentre li estrai da una funzione e non esiste un modo (carino) per catturare questa struttura.
Jørgen Tvedt

Risposte:


164

MODIFICARE

A partire da TypeScript 2.8 questo è ufficialmente possibile con ReturnType<T>.

type T10 = ReturnType<() => string>;  // string
type T11 = ReturnType<(s: string) => void>;  // void
type T12 = ReturnType<(<T>() => T)>;  // {}
type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>;  // number[]

Vedi qui per i dettagli.

TypeScript è fantastico!


Trucco della vecchia scuola

La risposta di Ryan non funziona più, sfortunatamente. Ma l'ho modificato con un hack di cui sono irragionevolmente contento. Ecco:

const fnReturnType = (false as true) && fn();

Funziona eseguendo il cast di false al valore letterale di true, in modo che il sistema di tipi pensi che il valore restituito sia il tipo della funzione, ma quando si esegue effettivamente il codice, cortocircuiti su false.

TypeScript è il migliore. : D


53
Gesù, è orribile.
John Weisz

for flow: const DUMMY = ((null: any): Object) && fn () export type Type = typeof DUMMY
David Xu

qualche idea del perché quando installo 2.8 e provo ottengo il seguente errore Cannot find name 'ReturnType':? utilizzando vscode
NSjonas

1
@NSjonas devi dire a vscode di usare l'altra versione. Penso che la barra di stato sia in basso a destra.
Meirion Hughes

12
Dovevo fare ReturnType<typeof fn>(la risposta implica che ReturnType<fn>è sufficiente).
spacek33z

49

Il modo più semplice in TypeScript 2.8:

const foo = (): FooReturnType => {
}

type returnType = ReturnType<typeof foo>;
// returnType = FooReturnType

5

Il codice seguente funziona senza eseguire la funzione. Proviene dalla libreria react-redux-typescript ( https://github.com/alexzywiak/react-redux-typescript/blob/master/utils/redux/typeUtils.ts )

interface Func<T> {
    ([...args]: any, args2?: any): T;
}
export function returnType<T>(func: Func<T>) {
    return {} as T;
}


function mapDispatchToProps(dispatch: RootDispatch, props:OwnProps) {
  return {
    onFinished() {
      dispatch(action(props.id));
    }
  }
}

const dispatchGeneric = returnType(mapDispatchToProps);
type DispatchProps = typeof dispatchGeneric;

4

Non c'è un modo per farlo (vedere https://github.com/Microsoft/TypeScript/issues/6606 per il monitoraggio dell'elemento di lavoro che aggiunge questo).

Una soluzione comune è scrivere qualcosa come:

var dummy = false && test();
type t2 = typeof dummy;

3
Non penso che questa soluzione alternativa funzionerà più, probabilmente a causa dell'analisi del tipo basata sul flusso di controllo.
JKillian

3
@JKillian hai ragione, l'esempio proposto non funziona più, anche se puoi facilmente ingannare TypeScript con !1invece di false- typescriptlang.org/play/…
DanielM

3

Modifica: non è più necessario con TS 2.8! ReturnType<F>fornisce il tipo restituito. Vedi risposta accettata.


Una variante su alcune delle risposte precedenti che sto usando, che funziona strictNullCheckse nasconde un po 'la ginnastica inferenziale:

function getReturnType<R>(fn: (...args: any[]) => R): R {
  return {} as R;
}

Utilizzo:

function foo() {
  return {
    name: "",
    bar(s: string) { // doesn't have to be shorthand, could be `bar: barFn` 
      return 123;
    }
  }
}

const _fooReturnType = getReturnType(foo);
export type Foo = typeof _fooReturnType; // type Foo = { name: string; bar(s: string): number; }

Si fa chiamare la getReturnTypefunzione, ma non chiamare la funzione originale. Si potrebbe evitare che la getReturnTypechiamata utilizzando (false as true) && getReturnType(foo), ma IMO questo solo rende più confusa.

Ho appena usato questo metodo con alcuni regexp trova / sostituisci per migrare un vecchio progetto Angular 1.x che aveva ~ 1500 funzioni di fabbrica scritte in questo modo, originariamente in JS, e ho aggiunto i Footipi ecc. A tutti gli usi ... incredibile quello con codice rotto troverà. :)


Grazie! Anche se triste richiede questa quantità di verbosità, almeno funziona!
ecmanaut

@ecmanaut Non ne abbiamo più bisogno! In TS 2.8 è possibile utilizzare ReturnType<F>per ottenere un tipo restituito da funzioni. :)
Aaron Beall

Le risposte qui divergono notevolmente dal substrato di esempio della domanda, rendendo difficile capire come produrre un tipo che sia il tipo restituito dalla funzione test. Questa risposta è stata una delle più utili per rinominato solo testa foo(e certamente produrre un tipo di ritorno più complessa, per calci). Sia la risposta accettata che il tuo commento sono probabilmente ottimi se sai già come applicarli all'esempio originale. (È tarda notte qui, e non mi è immediatamente evidente come sarebbe la sintassi di un esempio completo di ReturnType.)
ecmanaut

1

Se la funzione in questione è un metodo di una classe definita dall'utente, è possibile utilizzare decoratori di metodi in combinazione con Reflect Metadata per determinare il tipo di ritorno (funzione di costruzione) in fase di esecuzione (e con esso, fare come meglio credi).

Ad esempio, potresti registrarlo sulla console:

function logReturnType(
    target: Object | Function,
    key: string,
    descriptor: PropertyDescriptor
): PropertyDescriptor | void {
    var returnType = Reflect.getMetadata("design:returntype", target, key);

    console.log(returnType);
}

Basta agganciare questo decoratore di metodo su un metodo di tua scelta e avrai il riferimento esatto alla funzione di costruzione dell'oggetto che viene presumibilmente restituito dalla chiamata al metodo.

class TestClass {
    @logReturnType // logs Number (a string representation)
    public test(): number {
        return 42;
    }
}

Tuttavia, ci sono alcune limitazioni notevoli a questo approccio:

  • devi definire esplicitamente il tipo di ritorno su un metodo decorato come tale, altrimenti verrai indefinito da Reflect.getMetadata,
  • puoi fare riferimento solo a tipi effettivi che esistono anche dopo la compilazione; cioè niente interfacce o generici

Inoltre, dovrai specificare i seguenti argomenti della riga di comando per il compilatore dattiloscritto, perché sia ​​i decoratori che i metadati di riflessione sono funzionalità sperimentali al momento della stesura di questo post:

--emitDecoratorMetadata --experimentalDecorators

0

Non c'è modo di recuperare il tipo di ritorno della funzione senza eseguirlo tristemente. Questo perché quando TypeScript viene compilato in JS, si perdono tutte le informazioni relative al tipo.


3
Sebbene sia tecnicamente vero che TS perde le informazioni sul tipo durante il runtime, questo fatto non è rilevante, perché OP vorrebbe determinare il tipo della funzione in fase di compilazione. Questo è ancora più ovvio ora che ReturnType<T>è stato implementato in TypeScript.
gira il

0

Mi è venuto in mente quanto segue, che sembra funzionare bene:

function returnType<A, B, Z>(fn: (a: A, b: B) => Z): Z
function returnType<A, Z>(fn: (a: A) => Z): Z
function returnType<Z>(fn: () => Z): Z
function returnType(): any {
    throw "Nooooo"
}

function complicated(value: number): { kind: 'complicated', value: number } {
    return { kind: 'complicated', value: value }
}

const dummy = (false as true) && returnType(complicated)
type Z = typeof dummy

1
Non penso che gli argomenti della funzione debbano essere generici, dal momento che li stai buttando via comunque. fn: (...args: any[]) => Zé tutto quello di cui hai bisogno.
Aaron Beall
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.