网站搜索

带有泛型的 Typescript 箭头函数的语法错误


首先,这里有一个类似的问题:what-is-the-syntax-for-typescript-arrow-functions-with-generics

但是,我想知道语法错误的罪魁祸首。

我正在使用一个外部库,这就是定义文件 (index.d.ts) 的样子:

外部库的 index.d.ts

declare namespace Student {
    export interface Lecture {
        lectureName: string;
    }

    export interface Student {
        new (): Student;

        on1(eventName: string, callback: (<T>(lecture: T, oldLecture: T) => void) |
                                        ((name: string, ...args: any[]) => void)): void;

        on2(eventName: string, callback: (<T>(lecture: T, oldLecture: T) => void)): void;
    }
}

declare var Student: Student.Student;

declare module "student" {
    export = Student;
}

请注意,在 Student.Student 中有两个函数:on1 和 on2——函数 on1 的代码稍多一些。

所以这是我的代码示例。

案例 1

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', (lecture: Lecture, oldLecture: Lecture) => {
            // Argument of type error
        });

        this.student.on2('test', (lecture: Lecture, oldLecture: Lecture) => {
            // Argument of type error
        });
    }
}

函数 on1 报以下错误:

类型 '(lecture: Lecture, oldLecture: Lecture) => void' 的参数不可分配给类型 '((lecture: T, oldLecture: T) => void) | ((name: string, ...args: any[]) => void)' 的参数。 类型 '(lecture: Lecture, oldLecture: Lecture) => void' 不能赋值给类型 '(name: string, ...args: any[]) => void'。 参数 'lecture' 和 'name' 的类型不兼容。 类型 'string' 不能分配给类型 'Lecture'。

函数 on2 报以下错误:

类型 '(lecture: Lecture, oldLecture: Lecture) => void' 的参数不能赋给类型 '(lecture: T, oldLecture: T) => void' 的参数。参数 'lecture' 和 'lecture' 的类型不兼容。类型 'T' 不能赋给类型 'Lecture'。

我以为这个例子是实现代码的正确方式——但为什么会出错?

案例 2

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', <Lecture>(lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName; 
            // Error: Property 'lectureName' does not exist on type 'Lecture'
        });

        this.student.on2('test', <Lecture>(lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName;
            // Error: Property 'lectureName' does not exist on type 'Lecture'
        });
    }
}

在这个例子中,我在箭头函数前面放了 <Lecture>——所以实现中没有错误,但现在我根本无法使用 lecture.lectureName。为什么?

案例 3

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', <T extends Lecture>(lecture: T, oldLecture: T) => {
            lecture.lectureName; // Yay! No problem!
        });

        this.student.on2('test', <T extends Lecture>(lecture: T, oldLecture: T) => {
            // Argument of type error
        });
    }
}

所以这个例子有正确答案——然而,函数on2仍然会给出类型错误的参数,就像情况1的例子一样。既然函数on1没有问题,这应该没问题吧?

案例 4

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent { 
    student: Student.Student;

    constructor() {
        this.student = new Student();

        this.student.on1('test', () => () => (lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName; // Yay! No error!
        });

        this.student.on2('test', () => () => (lecture: Lecture, oldLecture: Lecture) => {
            lecture.lectureName; // Yay! No error!
        });
    }
}

我偶然发现了这个解决方案——两个功能都运行良好。但我完全不知道为什么会有效。

我花了一些时间试图通过查看这些参考资料弄清确切原因(因为我喜欢 TypeScript):

  • https://github.com/teppeis/typescript-spec-md/blob/master/en/ch04.md
  • https://basarat.gitbooks.io/typescript/content/docs/types/generics.html
  • https://github.com/Microsoft/TypeScript/issues/3323
  • 在 TypeScript 箭头函数中指定返回类型

但我仍然想知道这个问题的确切原因。

答案#1

你对如何声明一个泛型函数以及如何调用一个泛型函数有点困惑。

你可以用这个来概括你的问题:

// Define the Identity function type
// The result type = input type
type TIdentityFunc = <T>(input: T) => T;

// Implement the TIdentity function
// We followed the rule.
const identityImpl: TIdentityFunc = <T>(input: T) => input;

// Now we call this implementation
const num = identity(5);   // num is always number
const str = identity('hi') // str is always a string

在你的例子中,你实现了请求的回调,这意味着当有人调用这个回调时,她将知道参数的类型。

请记住,您不是在调用回调函数,您只是实现它!

所以你的代码应该看起来像这样:

import * as Student from 'student';
import { Lecture } from 'student';

export class MyStudent {
  student: Student.Student;

  constructor() {
    this.student = new Student();

    this.student.on1('test', <T>(l1: T | string, l2: T, ...args) => {
      // This is a bit complicated overloading, 
      // But it follows the rules of the declaration
    });

    this.student.on2('test', <T>(lecture: T, oldLecture: T) => {
      // Your only assumption is that lecture, and oldLecture are the same type
    });
  }
}

答案#2

我知道这不能直接回答你的问题,但你可以通过使用类方法来避免这个问题,而不是使用嵌套的匿名回调(这感觉很像2015年的做法)

type Handler = <T>(t: T) => void;

class Student {
  on1(s:string, callback:Handler) : void {
    callback<string>("hi")
  }
}

class MyStudent { 
    student: Student

    constructor() {
        this.student = new Student()
        this.student.on1('test', this.log)
    }

    log<T>(t:T) : void { 
         console.log("hi " + t)
    }
}