Uzyskanie zwracanego typu funkcji


107

Mam następującą funkcję:

function test(): number {
    return 42;
}

Mogę określić typ funkcji za pomocą typeof:

type t = typeof test;

Tutaj tbędzie () => number.

Czy istnieje sposób na uzyskanie zwracanego typu funkcji? Chciałbym tbyć numberzamiast () => number.


3
Nie, w każdym razie nie z typem. typeof zwróci tylko „function” ( moja wielkość liter może być błędna ).
— Igor

Byłoby wspaniale, gdyby stało się to możliwe. Czasami definiujesz typy, gdy wypychasz je z funkcji i nie ma (przyjemnego) sposobu na uchwycenie tej struktury.
— Jørgen Tvedt

Odpowiedzi:


176

EDYTOWAĆ

Począwszy od TypeScript 2.8 jest to oficjalnie możliwe w przypadku 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[]

Aby uzyskać szczegółowe informacje, zobacz to żądanie ściągnięcia do Microsoft / TypeScript .

TypeScript jest niesamowity!


Hack starej szkoły

Odpowiedź Ryana już niestety nie działa. Ale zmodyfikowałem go za pomocą hacka, z którego jestem nieuzasadniony zadowolony. Ujrzeć:

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

Działa na zasadzie rzutowania falsena wartość dosłowną funkcji true, więc system typów uważa, że ​​wartość zwracana jest typem funkcji, ale kiedy faktycznie uruchamiasz kod, następuje zwarcie false.


56
Jezu, to okropne.
— John Weisz

dla przepływu: const DUMMY = ((null: any): Object) && fn () typ eksportu Type = typeof DUMMY
— David Xu

jakiś pomysł, dlaczego po zainstalowaniu 2.8 i spróbuję pojawia się następujący błąd Cannot find name 'ReturnType':? przy użyciu vscode
— NSjonas

1
@NSjonas, musisz powiedzieć vscode, aby używał innej wersji. Myślę, że na pasku stanu w prawym dolnym rogu.
— Meirion Hughes,

14
Musiałem to zrobić ReturnType<typeof fn>(odpowiedź sugeruje, ReturnType<fn>że wystarczy).
— spacek33z

52

Najłatwiejszy sposób w TypeScript 2.8:

const foo = (): FooReturnType => {
}

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

5

Poniższy kod działa bez wykonywania funkcji. Pochodzi z biblioteki 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

Nie ma sposobu, aby to zrobić (zobacz https://github.com/Microsoft/TypeScript/issues/6606, aby uzyskać informacje o śledzeniu elementu roboczego).

Typowym obejściem jest napisanie czegoś takiego:

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

3
Myślę, że to obejście nie będzie już działać - prawdopodobnie z powodu analizy typu opartej na przepływie sterowania.
— JKillian,

3
@JKillian masz rację, przykład zaproponowano już nie działa, chociaż można łatwo oszukać maszynopis ze !1zamiast false- typescriptlang.org/play/...
— DanielM

3

Edycja: Nie jest to już potrzebne z TS 2.8! ReturnType<F>podaje typ zwrotu. Zobacz zaakceptowaną odpowiedź.


Wariant niektórych z poprzednich odpowiedzi, których używam, który działa strictNullChecksi trochę ukrywa gimnastykę wnioskowania:

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

Stosowanie:

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; }

To ma wywołać getReturnTypefunkcję, ale nie nazywamy pierwotną funkcję. Państwo mogłoby uniemożliwić getReturnTypepołączenie przy użyciu (false as true) && getReturnType(foo)ale IMO to właśnie czyni go bardziej mylące.

Właśnie użyłem tej metody z niektórymi wyrażeniami regularnymi znajdź / zamień, aby zmigrować stary projekt Angular 1.x, który miał ~ 1500 fabrycznych funkcji napisanych w ten sposób, pierwotnie w JS, i dodałem Footypy itp. Do wszystkich zastosowań ... niesamowity zepsuty kod znajdzie. :)


Dzięki! Chociaż jest to smutne, wymaga tyle słownictwa, ale przynajmniej działa!
— ecmanaut

@ecmanaut Już tego nie potrzebujemy! W TS 2.8 możesz użyć ReturnType<F>do uzyskania typu zwracanego funkcji. :)
— Aaron Beall

Odpowiedzi tutaj znacznie odbiegają od przykładowego podłoża pytania, co utrudnia ustalenie, jak utworzyć typ, który jest typem zwracanym przez funkcję test. Ta odpowiedź była jedną z bardziej przydatnych tylko testdo zmiany nazwy na foo(i co prawda do tworzenia bardziej złożonego typu powrotu, dla kopnięć). Zarówno zaakceptowana odpowiedź, jak i Twój komentarz są prawdopodobnie świetne, jeśli wiesz już, jak zastosować je do oryginalnego przykładu. (Jest tu późna noc i nie od razu wiadomo, jak wyglądałaby składnia pełnego przykładu ReturnType.)
— ecmanaut

1

Jeśli dana funkcja jest metodą klasy zdefiniowanej przez użytkownika, możesz użyć dekoratorów metod w połączeniu z Reflect Metadata w celu określenia typu zwracanego (funkcji konstruktora) w czasie wykonywania (a wraz z nią rób, co chcesz).

Na przykład możesz zalogować się do konsoli:

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

    console.log(returnType);
}

Po prostu przyciągnij dekorator metody do wybranej metody, a otrzymasz dokładne odniesienie do funkcji konstruktora obiektu, która rzekomo została zwrócona przez wywołanie metody.

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

Istnieje jednak kilka godnych uwagi ograniczeń tego podejścia:

  • musisz jawnie zdefiniować typ zwracany w metodzie udekorowanej jako taka, w przeciwnym razie otrzymasz undefined z Reflect.getMetadata,
  • możesz odwoływać się tylko do rzeczywistych typów, które również istnieją po kompilacji; to znaczy nie ma interfejsów ani typów ogólnych

Będziesz także musiał określić następujące argumenty wiersza poleceń dla kompilatora maszynopisu, ponieważ zarówno dekoratory, jak i metadane odzwierciedlające są funkcjami eksperymentalnymi w momencie pisania tego postu:

--emitDecoratorMetadata --experimentalDecorators

0

Wymyśliłem następujący, który wydaje się fajnie działać:

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
Nie sądzę, aby argumenty funkcji były ogólne, ponieważ i tak je wyrzucasz. fn: (...args: any[]) => Zto wszystko czego potrzebujesz.
— Aaron Beall
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.