前言
我是TypeScript的初学者,在阅读TypeScript官方手册指南的<函数>一章在<this参数在回调函数里>这一小节产生了疑惑
官方的文档提供的实例本身逻辑和运行结果都没有问题,但是我自己做了几个额外的测试,却产生了一些意想不到的问题.
开发环境:
VSCode 1.26.1 TypeScript 3.0.1
官方例子
这个例子用于在回调函数中如何提供this
正确的类型判断
定义一个回调接口:
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
定义一个回调类,该类提供一个方法当作回调函数:
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use this here because it's of type void!
console.log('clicked!');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);// uiElement在官方例子中就没有定义,我自己利用他提供了一个实现类
由于缺少了一个实现接口的类,所以我自己定义了一个类且实现了UIElement
接口,问题也是由这个引起的.
我的完整例子:
interface UIElement {
addClickListener(onclick: (this: void, e: string) => void): void;
}
class SideBar implements UIElement {
addClickListener(onclick: (this: void, e: string) => void): void {
onclick('event');
}
}
class Handler {
info: string = 'ok';
onClickGood(this:void,event:string):void{
console.log('clicked');
console.log(event);
}
}
let h: Handler = new Handler();
let uiElement: SideBar = new SideBar();
uiElement.addClickListener(h.onClickGood)
这个例子是可以运行且没有错误的,我提供了一个SideBar
类,唯一的改动是回调函数中Event
的类型改为了String
.
例子链接:
https://www.tslang.cn/docs/ha...
例子在页面下方的位置
问题
-
一个类实现了接口定义的获取回调函数的方法,类实现的方法有可能不校验其内部的参数结构(回调的具体类型)
interface UIElement { addClickListener(onclick: (this: void, e: string) => void): void; } class SideBar implements UIElement { addClickListener(onclick){ onclick('event'); } }
这样写是没有问题的,即使
SideBar
中的addClickListener
方法没有提供完整的类型验证,但是TypeScript会自动推测出来,证据如下class SideBar implements UIElement { addClickListener(onclick,hello){ onclick('event'); } }
我给这个方法多添加了一个参数,由于
UIElement
中没有定义,所以这里提示错误了,证明TypeScript的类型推断工作正常,这符合我们的预期,但是接下来他却工作的不正常了interface UIElement { addClickListener(onclick: (this: void, e: string) => void): void; } class SideBar implements UIElement { addClickListener(onclick){ onclick('event'); } } class Handler { info: string; onClickGood(this:Handler,event:string):void{ console.log('clicked!'); console.log(event); } } let h:Handler = new Handler(); let uiElement:SideBar = new SideBar(); uiElement.addClickListener(h.onClickGood)// 没有报错
在这个例子的最后一句中我们将
Handler
类的onClickGood
方法传递给了SideBar
的addClickListener
请注意,如果按照之前结构匹配的正常运转效果,这里回调函数的接受类型应该是onclick: (this: void, e: string) => void
但是我们传递过去的类型却是(this:Handler,event:string) => void
显然不是正确的,但是却没有报错.只有给
SideBar
强制添加和UIElement
接口一样的类型规则情况下才提示类型不匹配class SideBar implements UIElement { addClickListener(onclick: (this: void, e: string) => void):void{ onclick('event'); } }
class Handler { info: string; onClickGood(this:Handler,event:string):void{ // can't use this here because it's of type void! console.log('clicked!'); console.log(event); } } let h:Handler = new Handler(); let uiElement:SideBar = new SideBar(); uiElement.addClickListener(h.onClickGood)// 报错了类型不匹配
-
被接口定义的回调函数的返回值实际实现可以和接口定义的类型不一致
继续使用之前的例子:interface UIElement { addClickListener(onclick: (this: void, e: string) => void): void; } class SideBar implements UIElement { addClickListener(onclick: (this: void, e: string) => void) :void{ onclick('event'); } }
class Handler { info: string; onClickGood(this:void,event:string){ console.log('clicked!'); console.log(event); return 123; } } let h:Handler = new Handler(); let uiElement:SideBar = new SideBar(); uiElement.addClickListener(h.onClickGood)// 没有报错
注意这里的
Handler
中的onClickGood
他返回的是number
类型,编辑器提示也是number
类型,但是却可以通过最后一句的测试.
即使我们addClickListener
要求提供的函数的返回值为void
.除非给
onClickGood
也添加返回类型:class Handler { info: string; onClickGood(this:void,event:string):void{ // can't use this here because it's of type void! console.log('clicked!'); console.log(event); return 123; } }
这下return的值终于被判定为错误了,但是这么一来
addClickListener
中制定的回调函数类型规则岂不是被无视了
--strict
看到题主
let h: Handler = new Handler()
的写法,我估计题主来自Java背景。JavaScript全是自动推断,c++11也有auto
关键字,只有Java拖到了今年3月才给了var
。右边的类型是确定的,左边还要再多写一个类型声明,麻烦到这种程度的写法也就Java有了。Typescript希望在严谨和方便之间取得平衡,而不是纠结于数学上的正确,所以允许了一些(微软认为常用的)类型不兼容。如果想尽可能像Java一样严格,可以打开编译器的
--strict
选项。类实现的方法有可能不校验其内部的参数结构
这段代码会报错,报的什么错呢?
注意到两个
any
了吗?这里报错,实际上是参数数量的错误,参数类型检查被跳过了。SideBar
中的addClickListener
方法没有提供完整的类型签名,所以TypeScript直接不管参数类型了。如果从严谨的角度来看,很坑爹对吧。所以我们打开
--strict
。现在addClickListener(onclick)
也会报错了:如果说“接口都定义好了,参数还不能自动推断出来吗?”,确实是这样的,毕竟Java的lambda已经做到了。但总之TypeScript现在还不支持……微软:你来咬我呀~
被接口定义的回调函数的返回值实际实现可以和接口定义的类型不一致
It's a feature, not a bug. 让我们考虑
这是合法的。对Java来说不可理喻对不对。然而
对于函数返回值,
上面的定义太深奥了,讲人话:可以多给,不能少给。
() => void
是什么意思?你别指望我会返回什么东西,但我到底会返回什么,不关你事。为什么要这样设计?考虑
你可以指望我返回的东西有
username
属性,但我到底会返回什么,不关你事。这样很方便对不对?所以回到题主的问题
人话:我不需要回调函数有返回值,就算有,我也保证不用,所以你爱返回啥返回啥。