实际项目中会运到的 Typescript 回调函数、事件侦听的类型定义,如果刚碰到会一脸蒙真的,我就是
记得最早是在2014年遇上 Typescript 当时是完全看不上这东西的,甚至带着鄙视的心态,到不是因为它比原生 Js 要多写很多代码而是
作为一名前端老兵遇上 Typescript 的语法与类型就会让我想起刚工作时学习的 Flash Actionscript3.0 脚本时代。不能说是完全相同,简直是一模一样。
大约2006年 Adobe 的 flash 9 就开发了自己的新脚本语言 ActionScript 3 完全符合 ECMAScript 第四版规范, 也就是ES4
与当时代的 Javascript 还处于刀耕火種不同,在 Flash 编辑器中使用 ActionScript3 编写代码就有了比较完善的类型检测与类型提示。
曾经的 Actionscript3.0 辉煌的时代,那时动画有Flash, 应用有 Flex, 跨平台桌面应用有 Adobe air ,后面还支持移动端,这些用的都是 Actionscript3.0 脚本
而 Actionscript3.0 在我熟练掌握后退出了历史舞台。。。多棒的脚本语言啊,我又白学了。原因大致是 Adobe 的不上进和其它大公司的联合围剿
所以当我第一次接触到 Typescript 的时候内心非常抵触。
这几年 Javascript 跟其它语言相比可能还差一大截,但已和当年刀耕火種不同,前端工具与框架层出不穷,快速更新迭代 web 应用越来越复杂,前端工具越来越成熟,Typescript 的应用
也就水到渠成了。当在团队中使用 Typescript 虽然多写了点儿类型代码,但是好处太多了,可以说是用了就回不去了
我们这样的小角色怎能与时代洪流相抵呢,随波逐流吧,学吧学到废为止

如果你学过 Actionscript3 那么对 Typescript 中普通的,类、接口、继承、变量类型等概念与语法就会非常熟悉
唯一没有且用的比较广泛的概念当属 Typescirpt 中的 "泛型" , 泛型的理解与运用自我感觉是比较难的,但又不能不面对,只能多看多学了
我所学到与理解的也是看的其它人分享的资料,拾人牙慧
最讨厌别人写的文章、书,上来就是一堆概念和名词解释。把你绕的云里雾里
我希望的是从实际运用出发,从问题开始找解决方案。也就是学了干啥用,得学以致用才能更好的理解
以下假设你已经对 Typescript 已经有了一定的基础了解
如果你从未学过 Typescript 那么请退出先去学基础!
注册自定义事件,传入的回调函数,如果事件类型(事件名)对应的回调函数内回调参数不一样
那么回调函数的类型注释我们无能为力,只能用 any ,如下 addEvent 函数,用于注册事件
eventType 定义为 string 类型
listener 这个是函数 Function, 但由于事件类型有多种,对应的回调函数也有好多种
这就尬住了,暂时只能用 (...args: any[]) => any 来作为 listener 的类型
但这样还是没有办法明确 listener 里边有多少个具体的参数以及类型
// 自定义注册事件函数的类型注释
const addEvent = (eventType: string, listener:(...args: any[]) => any) => {
console.log(eventType, listener);
}
addEvent('eventTypeName1', () => {
})
如果是这样,那么 调用 addEvent 时回调函数是没有任何有用的提示的
尬住了是不是
eventType 不同,对应的 listener 也不同
这时就应该想是不是能用泛型来解决,泛型就是在传入的时候才确写具体的类型约束
type MyEventMap = {
'eventTypeName1': (a: string) => number
'eventTypeName2': (test: boolean) => string[]
}
const addEvent = <T extends keyof MyEventMap>(eventType: T, listener: MyEventMap[T]) => {
console.log(eventType, listener);
}
addEvent('eventTypeName1', (a) => {
return 1
})
这样就有提示了,看效果

两个关键点
比如你自己在写 Js 框架,其中需求是要实现 addEventListener 的代理函数,如何给这个代理函数写ts注释呢?
on('click', ()=> {}) 这样的方法,且能提示 Typescript 默认提供的类型,并约束 eventName 在dom事件
const on = (eventName: string, listener: (...args: any[]) => any) => {
console.log(eventName, listener);
}
这样写也通过了检测...那肯定不行,因为需求是约束为 dom 事件,但现在约束了eventName为 string
on('click', () => {
})
又尬住了,我们得在 ts 提供的 lib.dom.d.ts 文件内找答案
源码中找到 interface HTMLElement 的接口定义
addEventListener<K extends keyof HTMLElementEventMap>(type: K, listener: (this: HTMLElement, ev: HTMLElementEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
显然 HTMLElementEventMap 就是我们要找的通过 eventName 映射 具体回调的 Map
那就和上面自定义注册函数一样处理就可以了即
const on = <T extends keyof HTMLElementEventMap>(eventName: T, listener: (this: HTMLElement, ev: HTMLElementEventMap[T]) => any, options?: boolean | AddEventListenerOptions) => {
console.log(eventName, listener, options);
}
这样就都有正常的提示了
on('mousedown', (e) => {
console.log(e)
})

on('paste', (e) => {
console.log(e)
})

很多情况下,我们会给 fetch 请求回来的函数用 data as User[] 来主动告诉编译器返回数据的类型, 虽然能用,但不优雅

我们会顺着思路,我们试试给 fetch 请求函数作 ts 注释
一样先建一个 ResponseMap ,key 是 三、fetch 请求地址 value 是 fetch 返回的数据类型
type ResponseMap = {
'hello/world': number
'test/getlist': string[]
}
const get = async <T extends keyof ResponseMap>(url: T):Promise<ResponseMap[T]> => {
const response = await fetch(url);
return response.json();
}
测试一下
get('hello/world')

get('test/getlist')

试了一下挺完美,但是,但是,肯定没这么简单,请求地址很多情况下是带有参数的
get('test/getlist?a=1&b=2')
发现提示错误,通不过校验了

果然类型很麻烦。。。

!!!需要改进一下泛型匹配
const get = async <T extends keyof ResponseMap>(url: T | `${T}?${string}`):Promise<ResponseMap[T]> => {
const response = await fetch(url);
return response.json();
}
get('test/getlist?a=1&b=2')
这下可以通过校验了,提示也正常工作

关键在于
url: T | ${T}?${string}
这一句的改动, 通过字符串模板提取出 T 来
const addEvent = <EventType extends keyof MyEventMap>(eventType: EventType, listener: MyEventMap[EventType]) => {
console.log(eventType, listener);
}
const get = async <FetchUrl extends keyof ResponseMap>(url: FetchUrl | `${FetchUrl}?${string}`):Promise<ResponseMap[FetchUrl]> => {
const response = await fetch(url);
return response.json();
}
说明:以上知识是看到国外某个讲 typescript 的视频中学到的,没找到原视频内容。当然很多英文内容也没有翻译,我只是把理解的知识转化一下,所以才叫拾人牙慧么...
cnblogs.com/willian/
我想在一个没有Sass引擎的类中使用Sass颜色函数。我已经在项目中使用了sassgem,所以我认为搭载会像以下一样简单:classRectangleincludeSass::Script::FunctionsdefcolorSass::Script::Color.new([0x82,0x39,0x06])enddefrender#hamlengineexecutedwithcontextofself#sothatwithintemlateicouldcall#%stop{offset:'0%',stop:{color:lighten(color)}}endend更新:参见上面的#re
我有一个服务模型/表及其注册表。在表单中,我几乎拥有服务的所有字段,但我想在验证服务对象之前自动设置其中一些值。示例:--服务Controller#创建Action:defcreate@service=Service.new@service_form=ServiceFormObject.new(@service)@service_form.validate(params[:service_form_object])and@service_form.saverespond_with(@service_form,location:admin_services_path)end在验证@ser
我正在尝试用ruby中的gsub函数替换字符串中的某些单词,但有时效果很好,在某些情况下会出现此错误?这种格式有什么问题吗NoMethodError(undefinedmethod`gsub!'fornil:NilClass):模型.rbclassTest"replacethisID1",WAY=>"replacethisID2andID3",DELTA=>"replacethisID4"}end另一个模型.rbclassCheck 最佳答案 啊,我找到了!gsub!是一个非常奇怪的方法。首先,它替换了字符串,所以它实际上修改了
我有一些代码在几个不同的位置之一运行:作为具有调试输出的命令行工具,作为不接受任何输出的更大程序的一部分,以及在Rails环境中。有时我需要根据代码的位置对代码进行细微的更改,我意识到以下样式似乎可行:print"Testingnestedfunctionsdefined\n"CLI=trueifCLIdeftest_printprint"CommandLineVersion\n"endelsedeftest_printprint"ReleaseVersion\n"endendtest_print()这导致:TestingnestedfunctionsdefinedCommandLin
我正在阅读SandiMetz的POODR,并且遇到了一个我不太了解的编码原则。这是代码:classBicycleattr_reader:size,:chain,:tire_sizedefinitialize(args={})@size=args[:size]||1@chain=args[:chain]||2@tire_size=args[:tire_size]||3post_initialize(args)endendclassMountainBike此代码将为其各自的属性输出1,2,3,4,5。我不明白的是查找方法。当一辆山地自行车被实例化时,因为它没有自己的initialize方法
是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s
如何在Ruby中按名称传递函数?(我使用Ruby才几个小时,所以我还在想办法。)nums=[1,2,3,4]#Thisworks,butismoreverbosethanI'dlikenums.eachdo|i|putsiend#InJS,Icouldjustdosomethinglike:#nums.forEach(console.log)#InF#,itwouldbesomethinglike:#List.iternums(printf"%A")#InRuby,IwishIcoulddosomethinglike:nums.eachputs在Ruby中能不能做到类似的简洁?我可以只
说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时
我正在尝试将以下SQL查询转换为ActiveRecord,它正在融化我的大脑。deletefromtablewhereid有什么想法吗?我想做的是限制表中的行数。所以,我想删除少于最近10个条目的所有内容。编辑:通过结合以下几个答案找到了解决方案。Temperature.where('id这给我留下了最新的10个条目。 最佳答案 从您的SQL来看,您似乎想要从表中删除前10条记录。我相信到目前为止的大多数答案都会如此。这里有两个额外的选择:基于MurifoX的版本:Table.where(:id=>Table.order(:id).
我需要一个通过输入字符串进行计算的方法,像这样function="(a/b)*100"a=25b=50function.something>>50有什么方法吗? 最佳答案 您可以使用instance_eval:function="(a/b)*100"a=25.0b=50instance_evalfunction#=>50.0请注意,使用eval本质上是不安全的,尤其是当您使用外部输入时,因为它可能包含注入(inject)的恶意代码。另请注意,a设置为25.0而不是25,因为如果它是整数a/b将导致0(整数)。