实际项目中会运到的 Typescript 回调函数、事件侦听的类型定义,如果刚碰到会一脸蒙真的,我就是

这是第一次我自己对 Typescript 记录学习,所以得先说一下我与 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 也不同

这时就应该想是不是能用泛型来解决,泛型就是在传入的时候才确写具体的类型约束

  1. 先建一个用于映射的类型对象 MyEventMap, key 是 eventType 类型, value 是对应的 listener 类型
  2. 添加泛型 T
  3. 用 extends keyof MyEventMap 约束 T 在 MyEventMap 的 key 范围内,而key 范围又是通过 keyof 来提取的
  4. listener 的类型通过 MyEventMap[T] 来获取
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
})

这样就有提示了,看效果

两个关键点

  • extends 来约束 eventType
  • MyEventMap[T] 来获取具体的 listener 类型

二、代理 DOM 事件的类型注释


比如你自己在写 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 的 ts 注释


很多情况下,我们会给 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 来


最后,人家的建议是泛型也需要更友好的命名,T、K、R、等等都太不友好了,可以更具名化如下, 把范围名字变的更具体


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/

https://github.com/willian12345

最新文章

  1. python cmd下运行中文乱码 策略
  2. LeetCode —— Merge k Sorted Lists
  3. eclipse emacs
  4. VisualStudio一打开工程就崩溃-重打开output显示We were unable to automatically populate your Visual Studio Online accounts.
  5. Express 路由
  6. P1379 八数码问题
  7. HDU 5842 Lweb and String
  8. CSS的继承与优先级
  9. 《RESTful Web Services》第二章 识别资源
  10. C# MySql 操作类
  11. c#利用VM_COPYDATA实现进程间通信
  12. 循环多少次? 【杭电--HDOJ-1799】 附题+具体解释
  13. 用Visual Studio 2015 编写驱动之前一定要注意的问题!!!
  14. Device Mapper 代码分析
  15. 微信小程序开发学习(二)
  16. web页面锁屏初级尝试
  17. web中icon 图标问题
  18. 从源码编译InfluxDB
  19. mysql字符集问题,及排序规则
  20. JAVA中如何用接口实现多继承和多态 (非常好)

热门文章

  1. vue脚手架安装及依赖
  2. 关于linux上strongswan客户端的配置
  3. 论文解读(CDCL)《Cross-domain Contrastive Learning for Unsupervised Domain Adaptation》
  4. TypeScript 之 控制流分析(Control Flow Analysis)
  5. lv逻辑卷
  6. day36-ThreadLocal
  7. linux系统中安装虚拟机
  8. 未授权访问漏洞之Redis漏洞复现
  9. java中json字符串与实体类对象相互转换
  10. csrf跨站请求伪造、csrf校验策略、csrf相关装饰器、auth认证模块、auth认证相关模块及操作、扩展auth_user表