Typescript 回调函数、事件侦听的类型定义与注释--拾人牙慧
实际项目中会运到的 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 也不同
这时就应该想是不是能用泛型来解决,泛型就是在传入的时候才确写具体的类型约束
- 先建一个用于映射的类型对象 MyEventMap, key 是 eventType 类型, value 是对应的 listener 类型
- 添加泛型 T
- 用 extends keyof MyEventMap 约束 T 在 MyEventMap 的 key 范围内,而key 范围又是通过 keyof 来提取的
- 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
Typescript 回调函数、事件侦听的类型定义与注释--拾人牙慧的更多相关文章
- JS 的事件基础、事件侦听与抛发、
前言 JavaScript是一种事件驱动型语言.事件驱动是指JavaScript引擎并不是在看到代码之后就会立即执行,而是会在合适的时间才去执行.这个合适的时间是指当某个事件发生之后(例如一个输入框的 ...
- Android事件侦听器回调方法浅谈
http://developer.51cto.com/art/201001/180846.htm Android事件侦听器作为视图View类的接口,其中包含有不少回调方法,比如:onClick():o ...
- javascript中事件总结&通用的事件侦听器函数封装&事件委托
前言: JAVASCRIPT与HTML之间的交互是通过事件来实现的.事件,就是文档或浏览器窗口中发生的一些特定交互瞬间.可以使用侦听器( 或处理程序 )来预定事件,以便事件发生时执行相应的代码.这种在 ...
- Unity3D 自定义事件(事件侦听与事件触发)
先来看下效果图,图中点击 Cube(EventDispatcher),Sphere(EventListener)以及 Capsule(EventListener)会做出相应的变化,例子中的对象相互之间 ...
- Node js 安装+回调函数+事件
/* 从网站 https://nodejs.org/zh-cn/ 下载 这里用的 9.4.0 版本 下载完安装 安装目录是 D:\ApacheServer\node 一路默认安装 安装后打开cmd命令 ...
- JavaScript DOM高级程序设计 4.3控制事件流和注册事件侦听器--我要坚持到底!
一.事件流 我们通过下面一个实例,进行说明. <body> <h1>Event Flow</h1> <ul id="nav"> &l ...
- js事件流、事件处理程序/事件侦听器
1.事件流 事件冒泡 IE的事件流叫做事件冒泡(event bubbling),即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档). 事件捕获 ...
- nodejs-REPL/回调函数/事件循环
REPL 回调函数 事件循环 REPL----------------------------------------------------- Node.js REPL(Read Eval Prin ...
- 052_末晨曦Vue技术_处理边界情况之程序化的事件侦听器
程序化的事件侦听器 点击打开视频讲解更详细 现在,你已经知道了 $emit 的用法,它可以被 v-on 侦听,但是 Vue 实例同时在其事件接口中提供了其它的方法.我们可以: 通过 $on(event ...
- ArcGIS API for JavaScript 4.2学习笔记[7] 鹰眼(缩略图的实现及异步处理、Promise、回调函数、监听的笔记)
文前说明:关于style就是页面的css暂时不做评论,因为官方给的例子的样式实在太简单了,照抄阅读即可. 这篇文章有着大量AJS 4.x版本添加的内容,如监听watch.Promise对象.回调函数. ...
随机推荐
- Dev-Cpp下载与安装
目录 一.介绍 Dev-Cpp 二.下载 Dev-Cpp 1.通过百度网盘下载 2.通过 SourceForge 官网下载 三.安装 Dev-Cpp 写在结尾的话 免责声明 大家好,这里是 main工 ...
- jquery操作class
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- 【SQL知识】SQL中的join操作总结:内连接、外连接(左右全)
一.含义 基于表之间的共同字段,把来自两个或多个表的行结合起来 二.分类 内连接:join / inner join 外连接:left join / right join / full outer j ...
- 【每日一题】【DFS和回溯的区别】【BFS】104. 二叉树的最大深度-211227/220218
给定一个二叉树,找出其最大深度. 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数. 说明: 叶子节点是指没有子节点的节点. 示例:给定二叉树 [3,9,20,null,null,15,7], ...
- WinUI(WASDK)使用MediaPipe检查手部关键点并通过ML.NET进行手势分类
前言 之所以会搞这个手势识别分类,其实是为了满足之前群友提的需求,就是针对稚晖君的ElectronBot机器人的上位机软件的功能丰富,因为本来擅长的技术栈都是.NET,也刚好试试全能的.NET是不是真 ...
- Velero系列文章(四):使用Velero进行生产迁移实战
概述 目的 通过 velero 工具, 实现以下整体目标: 特定 namespace 在B A两个集群间做迁移; 具体目标为: 在B A集群上创建 velero (包括 restic ) 备份 B集群 ...
- python之元组(tuple)知识点
元组与列表都是容器,两个的区别在于: 1.元组使用的是小括号,列表使用的是方括号 2.元组一旦定义不可修改,而列表是可以随意变更 创建元组 元组的创建与列表大同小异,逗号在元组中充当了元组的灵魂,创建 ...
- week_6
Andrew Ng 机器学习笔记 ---By Orangestar Week_6 (1) In Week 6, you will be learning about systematically im ...
- js将时间戳转成时间格式
let start_time = 1653007401082, date = new Date(+start_time), Y = date.getFullYear() + '-', M = (dat ...
- Android的诞生
Android操作系统最初由Andy Rubin开发,刚开始主要支持手机,被Google收购后,对Android进行了改良,使其可以用于平板电脑等其它领域. 1.1.1 Android的发展史Andr ...