การรับประเภทการส่งคืนของฟังก์ชัน


101

ฉันมีฟังก์ชั่นดังต่อไปนี้:

function test(): number {
    return 42;
}

ฉันสามารถหาประเภทของฟังก์ชันได้โดยใช้typeof:

type t = typeof test;

ที่นี่จะเป็นt() => number

มีวิธีรับประเภทการส่งคืนของฟังก์ชันหรือไม่? ผมอยากtจะเป็นแทนnumber() => number


2
ไม่ไม่ใช่กับ typeof อย่างไรก็ตาม typeof จะคืนค่า "function" เท่านั้น (ตัวพิมพ์ใหญ่อาจผิด )
อิกอ

มันจะดีมากถ้าสิ่งนี้เป็นไปได้ บางครั้งคุณกำหนดประเภทเมื่อคุณผลักมันออกจากฟังก์ชันและไม่มีวิธีใด (ดี) ในการจับโครงสร้างนี้
Jørgen Tvedt

คำตอบ:


164

แก้ไข

ในขณะที่ TypeScript 2.8 เป็นไปได้อย่างเป็นทางการกับ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[]

ดูรายละเอียดได้ที่นี่

TypeScript สุดยอดมาก!


แฮ็คโรงเรียนเก่า

คำตอบของ Ryan ใช้ไม่ได้อีกต่อไปน่าเสียดาย แต่ฉันได้แก้ไขด้วยการแฮ็กซึ่งฉันมีความสุขอย่างไม่มีเหตุผล ดูเถิด:

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

มันทำงานโดยการแคสต์เท็จเป็นค่าตัวอักษรของจริงเพื่อให้ระบบประเภทคิดว่าค่าที่ส่งคืนเป็นประเภทของฟังก์ชัน แต่เมื่อคุณรันโค้ดจริงมันจะลัดวงจรบนเท็จ

TypeScript ดีที่สุด : ง


53
พระเยซูที่น่ากลัว
John Weisz

สำหรับ flow: const DUMMY = ((null: any): Object) && fn () ประเภทการส่งออก Type = typeof DUMMY
David Xu

มีความคิดว่าทำไมเมื่อฉันติดตั้ง 2.8 และลองรับข้อผิดพลาดต่อไปนี้: Cannot find name 'ReturnType'? ใช้ vscode
NSjonas

1
@NSjonas คุณต้องบอก vscode ให้ใช้เวอร์ชันอื่น ฉันคิดว่าในแถบสถานะเขาอยู่ด้านล่างขวา
Meirion Hughes

12
ฉันต้องทำReturnType<typeof fn>(คำตอบReturnType<fn>ก็เพียงพอแล้ว)
spacek33z

49

วิธีที่ง่ายที่สุดใน TypeScript 2.8:

const foo = (): FooReturnType => {
}

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

5

โค้ดด้านล่างทำงานได้โดยไม่ต้องเรียกใช้ฟังก์ชัน มันมาจากไลบรารี 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

ไม่มีวิธีการดำเนินการนี้ (ดูhttps://github.com/Microsoft/TypeScript/issues/6606สำหรับการติดตามรายการงานที่เพิ่มสิ่งนี้)

วิธีแก้ปัญหาทั่วไปคือเขียนสิ่งที่ชอบ:

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

3
ฉันไม่คิดว่าวิธีแก้ปัญหานี้จะใช้ได้ผลอีกต่อไป - อาจเป็นเพราะการวิเคราะห์ประเภทตามโฟลว์การควบคุม
JKillian

3
@JKillian คุณพูดถูกตัวอย่างที่เสนอไม่ได้ผลอีกต่อไปแม้ว่าคุณจะสามารถหลอก TypeScript ได้อย่างง่ายดาย!1แทนfalse- typescriptlang.org/play/…
DanielM

3

แก้ไข:สิ่งนี้ไม่จำเป็นกับ TS 2.8 อีกต่อไป! ReturnType<F>ให้ประเภทผลตอบแทน ดูคำตอบที่ยอมรับ


ตัวแปรของคำตอบก่อนหน้านี้ที่ฉันใช้ซึ่งใช้ได้ผลstrictNullChecksและซ่อนยิมนาสติกเชิงอนุมานไว้เล็กน้อย:

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

การใช้งาน:

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

มันไม่เรียกgetReturnTypeฟังก์ชั่น แต่มันก็ไม่ได้เรียกใช้ฟังก์ชันเดิม คุณสามารถป้องกันไม่ให้getReturnTypeใช้การโทรได้(false as true) && getReturnType(foo)แต่ IMO ทำให้สับสนมากขึ้น

ฉันเพิ่งใช้วิธีนี้กับ regexp ค้นหา / แทนที่เพื่อโยกย้ายโปรเจ็กต์ Angular 1.x เก่าที่มีฟังก์ชั่นโรงงาน ~ 1500 ที่เขียนแบบนี้เดิมใน JS และเพิ่มFooประเภทอื่น ๆ ให้กับการใช้งานทั้งหมด ... จะหา. :)


ขอบคุณ! ในขณะที่น่าเศร้าต้องใช้คำฟุ่มเฟือยจำนวนนี้อย่างน้อยก็ใช้ได้ผล!
ecmanaut

@ecmanaut เราไม่ต้องการสิ่งนี้อีกต่อไป! ใน TS 2.8 คุณสามารถใช้ReturnType<F>เพื่อรับประเภทการส่งคืนฟังก์ชัน :)
Aaron Beall

คำตอบที่นี่แตกต่างอย่างมากจากพื้นผิวตัวอย่างของคำถามทำให้ยากที่จะเข้าใจว่าจะสร้างประเภทที่เป็นประเภทที่ส่งคืนของฟังก์ชันtestได้อย่างไร คำตอบนี้เป็นหนึ่งในคนที่มีประโยชน์มากสำหรับการเปลี่ยนชื่อเท่านั้นtestที่จะfoo(และยอมรับการผลิตประเภทผลตอบแทนที่ซับซ้อนมากขึ้นเพื่อลูก) ทั้งคำตอบที่ได้รับการยอมรับและความคิดเห็นของคุณอาจดีมากหากคุณรู้วิธีนำไปใช้กับตัวอย่างต้นฉบับแล้ว (มันดึกแล้วที่นี่และไม่ชัดเจนสำหรับฉันในทันทีว่าไวยากรณ์ของตัวอย่าง ReturnType แบบเต็มจะมีลักษณะอย่างไร)
ecmanaut

1

หากฟังก์ชันที่เป็นปัญหาเป็นวิธีการของคลาสที่ผู้ใช้กำหนดคุณสามารถใช้เมธอดมัณฑนากรร่วมกับReflect Metadataเพื่อกำหนดประเภทการส่งคืน (ฟังก์ชันตัวสร้าง) ที่รันไทม์ (และทำตามที่เห็นสมควร)

ตัวอย่างเช่นคุณสามารถบันทึกลงในคอนโซล:

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

    console.log(returnType);
}

เพียงแค่ใช้ตัวตกแต่งวิธีนี้ตามวิธีการที่คุณเลือกและคุณมีการอ้างอิงที่แน่นอนไปยังฟังก์ชันตัวสร้างของวัตถุที่ถูกส่งกลับจากการเรียกใช้เมธอด

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

อย่างไรก็ตามมีข้อ จำกัด ที่น่าสังเกตบางประการสำหรับแนวทางนี้:

  • คุณจะต้องชัดเจนกำหนดประเภทการกลับมาอยู่กับวิธีการตกแต่งเป็นเช่นนี้มิฉะนั้นคุณจะได้รับการกำหนดจากReflect.getMetadata,
  • คุณสามารถอ้างอิงเฉพาะประเภทจริงซึ่งมีอยู่หลังจากการคอมไพล์ นั่นคือไม่มีอินเทอร์เฟซหรือข้อมูลทั่วไป

นอกจากนี้คุณจะต้องระบุอาร์กิวเมนต์บรรทัดคำสั่งต่อไปนี้สำหรับคอมไพเลอร์ typescript เนื่องจากทั้งตัวตกแต่งและข้อมูลเมตาที่สะท้อนเป็นคุณลักษณะทดลองในการเขียนโพสต์นี้:

--emitDecoratorMetadata --experimentalDecorators

0

ไม่มีวิธีใดในการดึงประเภทการส่งคืนของฟังก์ชันโดยไม่ดำเนินการอย่างน่าเศร้า เนื่องจากเมื่อ TypeScript ถูกคอมไพล์เป็น JS คุณจะสูญเสียข้อมูลทั้งหมดเกี่ยวกับประเภท


3
แม้ว่าในทางเทคนิคแล้ว TS จะสูญเสียข้อมูลประเภทในระหว่างรันไทม์ แต่ความจริงนี้ไม่เกี่ยวข้องเนื่องจาก OP ต้องการกำหนดประเภทของฟังก์ชันในเวลาคอมไพล์ สิ่งนี้ชัดเจนยิ่งขึ้นในขณะนี้ที่ReturnType<T>มีการใช้งานใน TypeScript
thedayturns

0

ฉันคิดสิ่งต่อไปนี้ซึ่งดูเหมือนว่าจะทำงานได้ดี:

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
ฉันไม่คิดว่าอาร์กิวเมนต์ของฟังก์ชันจะต้องเป็นแบบทั่วไปเนื่องจากคุณจะทิ้งมันไป fn: (...args: any[]) => Zคือทั้งหมดที่คุณต้องการ
Aaron Beall
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.