Lấy kiểu trả về của một hàm


101

Tôi có chức năng sau:

function test(): number {
    return 42;
}

Tôi có thể lấy loại hàm bằng cách sử dụng typeof:

type t = typeof test;

Đây, tsẽ được () => number.

Có cách nào để lấy kiểu trả về của hàm không? Tôi muốn tđược numberthay vì () => number.


2
Không, dù sao đi nữa thì không. typeof sẽ chỉ trả về "chức năng" (cách viết hoa của tôi có thể sai ).
Igor

Sẽ thật tuyệt nếu điều này trở nên khả thi. Đôi khi bạn xác định các kiểu khi bạn đẩy chúng ra khỏi một hàm và không có cách nào (hay) để nắm bắt cấu trúc này.
Jørgen Tvedt

Câu trả lời:


164

BIÊN TẬP

Kể từ TypeScript 2.8, điều này chính thức có thể thực hiện được với 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[]

Xem chi tiết tại đây .

TypeScript thật tuyệt vời!


Hack trường cũ

Rất tiếc, câu trả lời của Ryan không còn hiệu quả nữa. Nhưng tôi đã sửa đổi nó bằng một bản hack mà tôi rất vui một cách phi lý. Hãy chứng kiến:

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

Nó hoạt động bằng cách ép sai thành giá trị theo nghĩa đen của true, để hệ thống kiểu nghĩ rằng giá trị trả về là kiểu của hàm, nhưng khi bạn thực sự chạy mã, nó ngắn mạch về sai.

TypeScript là tốt nhất. : D


53
Chúa ơi, thật kinh khủng.
John Weisz

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

bất kỳ ý tưởng tại sao khi tôi cài đặt 2.8 và thử tôi gặp lỗi sau Cannot find name 'ReturnType':? sử dụng vscode
NSjonas

1
@NSjonas bạn cần nói vscode để sử dụng phiên bản khác. Tôi nghĩ không phải anh ấy thanh trạng thái ở phía dưới bên phải.
Meirion Hughes

12
Tôi đã phải làm ReturnType<typeof fn>(câu trả lời ngụ ý ReturnType<fn>là đủ).
spacek33z

49

Cách dễ nhất trong TypeScript 2.8:

const foo = (): FooReturnType => {
}

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

5

Đoạn mã dưới đây hoạt động mà không cần thực thi chức năng. Nó từ thư viện react-redux-stylescript ( 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

Không có cách nào để thực hiện việc này (xem https://github.com/Microsoft/TypeScript/issues/6606 để biết thêm tính năng này).

Một giải pháp phổ biến là viết một cái gì đó như:

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

3
Tôi không nghĩ rằng cách giải quyết này sẽ hoạt động nữa - có thể là do phân tích loại dựa trên luồng điều khiển.
JKillian

3
@JKillian bạn là đúng, ví dụ đề xuất không có tác phẩm lâu hơn, mặc dù bạn có thể dễ dàng đánh lừa nguyên cảo với !1thay vì false- typescriptlang.org/play/...
DanielM

3

Chỉnh sửa: Điều này không cần thiết với TS 2.8 nữa! ReturnType<F>đưa ra kiểu trả về. Xem câu trả lời được chấp nhận.


Một biến thể của một số câu trả lời trước đây mà tôi đang sử dụng, hoạt động strictNullChecksvà ẩn một chút về thể dục dụng cụ suy luận:

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

Sử dụng:

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

không gọi getReturnTypechức năng, nhưng nó không gọi hàm gốc. Bạn có thể ngăn getReturnTypecuộc gọi bằng cách sử dụng(false as true) && getReturnType(foo) IMO, điều này chỉ làm cho nó trở nên khó hiểu hơn.

Tôi vừa sử dụng phương pháp này với một số regexp find / Replace để di chuyển một dự án Angular 1.x cũ có ~ 1500 hàm gốc được viết như thế này, ban đầu bằng JS và thêm các Fooloại vv vào tất cả các mục đích sử dụng ... sẽ tìm thấy. :)


Cảm ơn! Mặc dù đáng buồn là nó phải mất một lượng thời gian dài như vậy, nhưng ít nhất nó cũng hoạt động!
ecmanaut

@ecmanaut Chúng tôi không cần cái này nữa! Trong TS 2.8, bạn có thể sử dụng ReturnType<F>để nhận kiểu trả về của các hàm. :)
Aaron Beall

Các câu trả lời ở đây khác nhau rất nhiều so với cơ sở ví dụ của câu hỏi, làm cho khó tìm ra cách tạo ra một kiểu là kiểu trả về của hàm test. Câu trả lời này là một trong những câu trả lời hữu ích hơn khi chỉ đổi tên testthành foo(và phải thừa nhận là tạo ra kiểu trả về phức tạp hơn, cho các cú đá). Cả câu trả lời được chấp nhận và nhận xét của bạn có lẽ rất tuyệt nếu bạn đã biết cách áp dụng chúng vào ví dụ ban đầu. (Ở đây là đêm muộn và tôi không rõ cú pháp của một ví dụ ReturnType đầy đủ sẽ trông như thế nào.)
ecmanaut

1

Nếu hàm được đề cập là một phương thức của một lớp do người dùng xác định, bạn có thể sử dụng trình trang trí phương thức trong phép liên hợp với Siêu dữ liệu phản chiếu để xác định kiểu trả về (hàm tạo) trong thời gian chạy (và với nó, làm như bạn thấy phù hợp).

Ví dụ: bạn có thể đăng nhập nó vào bảng điều khiển:

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

    console.log(returnType);
}

Chỉ cần chụp bộ trang trí phương thức này vào một phương thức bạn chọn và bạn có tham chiếu chính xác đến hàm khởi tạo của đối tượng được cho là trả về từ lệnh gọi phương thức.

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

Tuy nhiên, có một số hạn chế đáng chú ý đối với phương pháp này:

  • bạn cần phải xác định rõ ràng kiểu trả về trên một phương thức được trang trí như vậy, nếu không, bạn sẽ không được xác định từ Reflect.getMetadata,
  • bạn chỉ có thể tham chiếu các kiểu thực tế cũng tồn tại sau khi biên dịch; nghĩa là, không có giao diện hoặc số liệu chung

Ngoài ra, bạn sẽ cần chỉ định các đối số dòng lệnh sau cho trình biên dịch typecript, vì cả trình trang trí và siêu dữ liệu phản ánh đều là các tính năng thử nghiệm khi viết bài đăng này:

--emitDecoratorMetadata --experimentalDecorators

0

Không có cách nào để lấy kiểu trả về của hàm mà không thực thi nó. Điều này là do khi TypeScript được biên dịch thành JS, bạn sẽ mất tất cả thông tin liên quan đến loại.


3
Mặc dù về mặt kỹ thuật, TS mất thông tin kiểu trong thời gian chạy, nhưng thực tế này không liên quan, bởi vì OP muốn xác định kiểu của hàm tại thời điểm biên dịch. Điều này thậm chí còn rõ ràng hơn bây giờ ReturnType<T>đã được thực hiện trong TypeScript.
thedayturns

0

Tôi đã nghĩ ra những điều sau đây, có vẻ hoạt động tốt:

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
Tôi không nghĩ rằng các args hàm cần phải chung chung, vì dù sao thì bạn cũng đang vứt bỏ chúng. fn: (...args: any[]) => Zlà tất cả những gì bạn cần.
Aaron Beall
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.