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

Posted

技术标签:

【中文标题】带有泛型的 Typescript 箭头函数的语法错误【英文标题】:Syntax error for Typescript arrow functions with generics 【发布时间】:2018-01-31 06:14:22 【问题描述】:

首先这里有一个类似的问题: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中有两个函数:on1on2——函数on1有多一点代码。

这是我的代码示例。


案例一

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' 的类型不兼容。 类型“字符串”不可分配给类型“讲座”。

函数 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'
        );
    

在这个例子中,我把&lt;Lecture&gt;放在了箭头函数的前面——所以实现中没有错误,但现在我根本不能使用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 Specify return type in TypeScript arrow function

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

【问题讨论】:

【参考方案1】:

我知道这不能直接回答你的问题,但你可以通过使用类方法来避免这个问题,而不是嵌套匿名回调(感觉很 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)
    

【讨论】:

【参考方案2】:

您对如何声明泛型函数以及如何调用泛型函数感到有些困惑。

你可以用这个总结你的问题:

// 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
    );
  

【讨论】:

非常感谢您的评论和示例代码!我有一个问题 - 在你的例子中,我怎样才能写出没有错误的代码?例如,如果我写了这样的代码:lecture.lectureName,它表示该属性不存在,就像案例 2。 根据您的声明,您应该使用两个具有相同类型的参数来实现回调,仅此而已。什么都没有说 Lecture 对象...这就是 TypeScript 告诉您您未实现请求的声明的原因。顺便说一句:在案例 2 中,您只是将通用命名为“Lecture”而不是“T”。请记住,您不是此函数的调用者,您只需要实现此通用函数。调用者将使用两个具有相同类型的参数调用您的回调。【参考方案3】:

问题是打字在错误的地方声明了泛型:

declare interface Lecture 
  lectureName: string;


declare interface Student 
  new (): Student;

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


let s: Student;

s.on1('x', (a: Lecture, b: Lecture) => 

)
s.on2('y', (a: string, b: string) => 

)

【讨论】:

感谢您的评论 - 但不幸的是,我不能(也不想)更改声明文件,因为它是第 3 方库。另外,代码declare interface 将覆盖现有定义 其他人会遇到与您相同的问题。这是第 3 方开源吗?最好至少在那里打开一个问题,以便作者可以修复它。

以上是关于带有泛型的 Typescript 箭头函数的语法错误的主要内容,如果未能解决你的问题,请参考以下文章

带有泛型的 Typescript JSX - 参数隐式具有“任何”类型

如何在带有 React 的 Typescript/JSX 中使用带有箭头函数的泛型?

从 JavaScript 到 TypeScript - 接口

TypeScript 中泛型的不安全隐式转换

带有泛型的 C# 不寻常的继承语法

带有可选参数的 TypeScript lambda 函数