Typescript 回调函数、事件侦听的类型定义与注释--拾人牙慧

简介: 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 也不同

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

  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

相关文章
|
18天前
|
设计模式 JavaScript 安全
TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等
本文深入探讨了TypeScript性能优化及代码质量提升的重要性、方法与策略,包括合理使用类型注解、减少类型断言、优化模块导入导出、遵循编码规范、加强代码注释等,旨在帮助开发者在保证代码质量的同时,实现高效的性能优化,提升用户体验和项目稳定性。
40 6
|
17天前
|
开发框架 JavaScript 前端开发
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势
TypeScript 是一种静态类型的编程语言,它扩展了 JavaScript,为 Web 开发带来了强大的类型系统、组件化开发支持、与主流框架的无缝集成、大型项目管理能力和提升开发体验等多方面优势。通过明确的类型定义,TypeScript 能够在编码阶段发现潜在错误,提高代码质量;支持组件的清晰定义与复用,增强代码的可维护性;与 React、Vue 等框架结合,提供更佳的开发体验;适用于大型项目,优化代码结构和性能。随着 Web 技术的发展,TypeScript 的应用前景广阔,将继续引领 Web 开发的新趋势。
31 2
|
25天前
|
JavaScript 安全 前端开发
TypeScript类型声明:基础与进阶
通过本文的介绍,我们详细探讨了TypeScript的基础与进阶类型声明。从基本数据类型到复杂的泛型和高级类型,TypeScript提供了丰富的工具来确保代码的类型安全和可维护性。掌握这些类型声明能够帮助开发者编写更加健壮和高效的代码,提高开发效率和代码质量。希望本文能为您在使用TypeScript时提供实用的参考和指导。
32 2
|
1月前
|
JavaScript 开发者
在 Babel 插件中使用 TypeScript 类型
【10月更文挑战第23天】可以在 Babel 插件中更有效地使用 TypeScript 类型,提高插件的开发效率和质量,减少潜在的类型错误。同时,也有助于提升代码的可理解性和可维护性,使插件的功能更易于扩展和升级。
|
2月前
|
JavaScript 前端开发
TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第11天】TypeScript【类型别名、泛型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
2月前
|
JavaScript 前端开发 安全
TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
【10月更文挑战第9天】TypeScript【基础类型】超简洁教程!再也不用看臭又长的TypeScript文档了!
|
2月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与最佳实践
【10月更文挑战第8天】深入理解TypeScript:类型系统与最佳实践
|
2月前
|
移动开发 JavaScript 前端开发
TypeScript:数组类型&函数使用&内置对象
本文介绍了 TypeScript 中的数组类型、对象数组、二维数组、函数、函数重载、内置对象等概念,并通过代码示例详细展示了它们的使用方法。还提供了一个使用 HTML5 Canvas 实现的下雨效果的小案例。
|
1月前
|
JavaScript 前端开发 安全
TypeScript进阶:类型系统与高级类型的应用
【10月更文挑战第25天】TypeScript作为JavaScript的超集,其类型系统是其核心特性之一。本文通过代码示例介绍了TypeScript的基本数据类型、联合类型、交叉类型、泛型和条件类型等高级类型的应用。这些特性不仅提高了代码的可读性和可维护性,还帮助开发者构建更健壮的应用程序。
29 0
|
2月前
|
JavaScript 前端开发 开发者
深入理解TypeScript:类型系统与实用技巧
【10月更文挑战第8天】深入理解TypeScript:类型系统与实用技巧