10 个关于 TypeScript 的小技巧

全栈前端精选

共 5467字,需浏览 11分钟

 ·

2020-12-13 09:13

英文 | https://www.sangle7.com/


1、 TypeScript 和 DOM


当你开始使用 TypeScript 时,你会发现在浏览器环境中使用它,你需要非常了解它。假设我想在页面搜索框里找到一个元素:


const textEl = document.querySelector('inpot');console.log(textEl.value);// ? Property 'value' does not exist on type 'Element'.


Oops…… 抛出了一个错误,因为我把 ‘input’ 打成了 ‘inpot’
它怎么知道的?答案在于 lib.dom.d.ts 文件,该文件是 TypeScript 库的一部分,并且基本上描述了浏览器中发生的所有事情(对象,函数,事件)。 
该定义的一部分是在 querySelector 方法的输入中使用的接口,并将特定的字符串文字(例如’div’, ‘table’或’input’)映射到相应的 HTML 元素类型:


interface HTMLElementTagNameMap {  a: HTMLAnchorElement;  abbr: HTMLElement;  address: HTMLElement;  applet: HTMLAppletElement;  area: HTMLAreaElement;  article: HTMLElement;  /* ... */  input: HTMLInputElement;  /* ... */}


这不是一个完美的解决方案,因为它仅适用于基本元素选择器,但总比没有好,对吧?
这种’智能’TypeScript 行为的另一个示例是在处理浏览器事件时:


textEl.addEventListener('click', (e) => {  console.log(e.clientX);});


上面的示例中的.clientX 在任何给定事件上都不可用-仅在 MouseEvent 上可用。然后 TypeScript 根据作为 addEventListener 方法中第一个参数的“click”文字确定事件的类型。


2、期望泛型


因此,如果您使用其他任何东西而不是元素选择器:


document.querySelector('input.action')


那么 HTMLELementTagNameMap 将不再有用,TypeScript 只会返回一个相当基本的 Element 类型。
与 querySelector 一样,函数通常可以返回各种不同的结构,而 TypeScript 不可能确定将是哪种结构。在那种情况下,您可以非常期待,该函数也是通用的,并且可以使用方便的通用语法提供该返回类型:


textEl = document.querySelector < HTMLInputElement > 'input.action';console.log(textEl.value);// ? 'value' is available because we've instructed TS// about the type the 'querySelector' function works with.

3、“我们真的找到了吗?”


该 document.querySelector(…)方法实际上并不总是返回一个对象,是吗?与选择器匹配的元素可能不在页面上-函数将返回 null 而不是对象。因此,默认情况下,访问.value 属性可能不会保存所有内容。
默认情况下,类型检查器认为 null 和 undefined 可分配给任何类型。您可以通过在 tsconfig.json 中添加严格的 null 检查来使其更加安全并限制这种行为:


{  "compilerOptions": {    "strictNullChecks": true  }}


使用该设置后,如果您尝试访问可能为 null 的对象上的属性,TypeScript 将会报错,并且你将不得不确保该对象的存在,例如 通过用 if(textEl){...} 条件包装该部分。
除了 querySelector 之外,另一个流行的例子是 Array.find 方法,其结果可能是不确定的。
您并非总能找到想要的东西:-)
4、“TS,我告诉你,在这里!”
正如我们已经确定的那样,通过严格的 null 检查,TypeScript 将更加怀疑我们的价值观。另一方面,有时您仅从外部就知道将设置该值。在这种特殊情况下,您可以使用“后缀表达式运算符”:


const textEl = document.querySelector('input');console.log(textEl!.value);// ? with "!" we assure TypeScript// that 'textEl' is not null/undefined

5、当迁移到 TS…


通常,当您具有要迁移到 TypeScript 的旧版代码库时,更大的麻烦之一就是使 id 遵守您的 TSLint 规则。您可以做的是通过添加以下内容来编辑所有这些文件


// tslint:disable


在每个文件的第一行中,这样 TSLint 不会真正检查它们。然后,仅当开发人员处理旧文件时,他才会删除此注释并仅修复该文件中的所有掉毛错误。这样一来,我们就不会进行革命,而只会进行进化-代码库会逐渐但安全地得到改善。
至于将实际类型添加到旧的 JavaScript 代码中,实际上通常可以不这样做。只有在您有一些令人讨厌的代码(例如, 为同一变量分配不同类型的值,您可能会遇到问题。如果重构不是一个小问题,您可以使用这个方法解决问题:


let mything = 2;mything = 'hi';// ? Type '"hi"' is not assignable to type 'number'mything = 'hi' as any;// ? if you say "any", TypeScript says ¯\_(ツ)_/¯


但是真的,真的,真的将其用作最后的手段。我们不喜欢TypeScript中的 any。


6、更多限制


有时TypeScript无法推断类型。最常见的情况是一个函数参数:


function fn(param) {    console.log(param);}


在内部,它需要在此处为param分配某种类型,因此它可以分配任何类型。由于我们希望将any限制为绝对最小值,因此通常建议使用另一个tsconfig.json设置来限制该行为:


{    "compilerOptions": {        "noImplicitAny": true    }}


不幸的是,我们不能在函数返回类型上使用这种安全带(需要明确输入)。因此,如果改为使用函数fn(param):string {我会忘记该类型(函数fn(param){),TypeScript将不会关注我返回的内容,即使我从该函数返回了任何内容。更准确地说:它将根据您退回或未退回的商品推断出退货价值。
幸运的是,TSLint可以为您提供帮助。使用typedef规则,您可以使返回类型成为必需:


{    "rules": {        "typedef": [            true,            "call-signature"        ]    }}


这看起来是个好主意!


7、类型保护


当值具有多种类型时,必须在算法中将其考虑在内,以区分一种类型与另一种类型。关于TypeScript的事情是它了解这种逻辑。


type BookId = number | string;function returnFormatterId(id: BookId) {    return id.toUpperCase();    // ? 'toUpperCase' does not exist on type 'number'.}function returnFormatterId(id: BookId) {    if (typeof id === 'string') {        // we've made sure it's a string:        return id.toUpperCase(); // so it's ?    }    // ? TS also understands that it    // has to be a number here:    return id.toFixed(2)}

8、再谈泛型


假设我们具有这种相当通用的结构:


interface Bookmark {    id: string;}class BookmarksService {    items: Bookmark[] = [];}


您想在不同的应用程序中使用它,例如 用于存储书籍或电影。
在这样的应用程序中,您可以执行以下操作:


interface Movie {    id: string;    name: string;}class SearchPageComponent {    movie: Movie;    constructor(private bs: BookmarksService) {}    getFirstMovie() {        // ? types are not assignable        this.movie = this.bs.items[0];        // ? so you have to manually assert type:        this.movie = this.bs.items[0] as Movie;    }    getSecondMovie() {        this.movie = this.bs.items[1] as Movie;    }}


在该类中可能需要多次这种类型声明。
我们可以做的是将 BookmarksService 类定义为通用类:


class BookmarksService {    items: T[] = [];}


好吧,不过现在它太通用了……我们要确保此类使用的类型能够满足Bookmark接口(即具有id:string属性)。这是改进的声明:


class BookmarksService {    items: T[] = [];}


现在,在我们的SearchPageComponent中,我们只需指定一次类型:


class SearchPageComponent {    movie: Movie;    constructor(private bs: BookmarksService) {}    getFirstMovie() {        this.movie = this.bs.items[0]; // ?    }    getSecondMovie() {        this.movie = this.bs.items[1]; // ?    }}


对于该通用类,还有一项可能是有用的改进-如果您以这种通用身份在其他地方使用它,而又不想编写BookmarksService 的话。
您可以在泛型的定义中提供默认类型:


class BookmarksService {    items: T[] = [];}const bs = new BookmarksService();// I don't have to provide the type for the generic now// - in that case 'bs' will be of that default type 'Bookmark'

9、路由参数

export interface DashboardRouteParams {  countryId: string;  deviceId: string;}
const routes: Routes = [{ path: 'country/:countryId/device/:deviceId/dashboard'}]

10、根据 API 响应创建 Interface


如果您收到来自API的大量嵌套响应,那么手动键入相应的接口确实很麻烦。这是应该由机器处理的任务!?
有两种选择:


  • http://www.json2ts.com
  • http://www.jsontots.com


是其中的一些,但是坦率地说,它们的服务器通常不可用。由于URL的记忆力很强,我通常只是从它们开始:-)为了获得最佳结果和一些其他选项,请使用


  • https://app.quicktype.io/


它还提供了一个方便的Visual Studio Code插件~
- END -




分享前端好文,点亮 在看


浏览 43
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报