前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >10 个关于 TypeScript 的小技巧

10 个关于 TypeScript 的小技巧

作者头像
Nealyang
发布2020-12-15 16:38:43
1.3K0
发布2020-12-15 16:38:43
举报
文章被收录于专栏:全栈前端精选

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

1、 TypeScript 和 DOM

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

代码语言:javascript
复制
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 元素类型:

代码语言:javascript
复制
interface HTMLElementTagNameMap {
  a: HTMLAnchorElement;
  abbr: HTMLElement;
  address: HTMLElement;
  applet: HTMLAppletElement;
  area: HTMLAreaElement;
  article: HTMLElement;
  /* ... */
  input: HTMLInputElement;
  /* ... */
}

这不是一个完美的解决方案,因为它仅适用于基本元素选择器,但总比没有好,对吧?

这种’智能’TypeScript 行为的另一个示例是在处理浏览器事件时:

代码语言:javascript
复制
textEl.addEventListener('click', (e) => {
  console.log(e.clientX);
});

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

2、期望泛型

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

代码语言:javascript
复制
document.querySelector('input.action')

那么 HTMLELementTagNameMap 将不再有用,TypeScript 只会返回一个相当基本的 Element 类型。

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

代码语言:javascript
复制
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 检查来使其更加安全并限制这种行为:

代码语言:javascript
复制
{
  "compilerOptions": {
    "strictNullChecks": true
  }
}

使用该设置后,如果您尝试访问可能为 null 的对象上的属性,TypeScript 将会报错,并且你将不得不确保该对象的存在,例如 通过用 if(textEl){...} 条件包装该部分。

除了 querySelector 之外,另一个流行的例子是 Array.find 方法,其结果可能是不确定的。

您并非总能找到想要的东西:-)

4、“TS,我告诉你,在这里!”

正如我们已经确定的那样,通过严格的 null 检查,TypeScript 将更加怀疑我们的价值观。另一方面,有时您仅从外部就知道将设置该值。在这种特殊情况下,您可以使用“后缀表达式运算符”:

代码语言:javascript
复制
const textEl = document.querySelector('input');
console.log(textEl!.value);
// ? with "!" we assure TypeScript
// that 'textEl' is not null/undefined

5、当迁移到 TS…

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

代码语言:javascript
复制
// tslint:disable

在每个文件的第一行中,这样 TSLint 不会真正检查它们。然后,仅当开发人员处理旧文件时,他才会删除此注释并仅修复该文件中的所有掉毛错误。这样一来,我们就不会进行革命,而只会进行进化-代码库会逐渐但安全地得到改善。

至于将实际类型添加到旧的 JavaScript 代码中,实际上通常可以不这样做。只有在您有一些令人讨厌的代码(例如, 为同一变量分配不同类型的值,您可能会遇到问题。如果重构不是一个小问题,您可以使用这个方法解决问题:

代码语言: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无法推断类型。最常见的情况是一个函数参数:

代码语言:javascript
复制
function fn(param) {
    console.log(param);
}

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

代码语言:javascript
复制
{
    "compilerOptions": {
        "noImplicitAny": true
    }
}

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

幸运的是,TSLint可以为您提供帮助。使用typedef规则,您可以使返回类型成为必需:

代码语言:javascript
复制
{
    "rules": {
        "typedef": [
            true,
            "call-signature"
        ]
    }
}

这看起来是个好主意!

7、类型保护

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

代码语言:javascript
复制
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、再谈泛型

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

代码语言:javascript
复制
interface Bookmark {
    id: string;
}
class BookmarksService {
    items: Bookmark[] = [];
}

您想在不同的应用程序中使用它,例如 用于存储书籍或电影。

在这样的应用程序中,您可以执行以下操作:

代码语言:javascript
复制
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 类定义为通用类:

代码语言:javascript
复制
class BookmarksService<T> {
    items: T[] = [];
}

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

代码语言:javascript
复制
class BookmarksService<T extends Bookmark> {
    items: T[] = [];
}

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

代码语言:javascript
复制
class SearchPageComponent {
    movie: Movie;
    constructor(private bs: BookmarksService<Movie>) {}
    getFirstMovie() {
        this.movie = this.bs.items[0]; // ?
    }
    getSecondMovie() {
        this.movie = this.bs.items[1]; // ?
    }
}

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

您可以在泛型的定义中提供默认类型:

代码语言:javascript
复制
class BookmarksService<T extends Bookmark = Bookmark> {
    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、路由参数

代码语言:javascript
复制
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 -

代码语言:javascript
复制
分享前端好文,点亮 在看
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 全栈前端精选 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、 TypeScript 和 DOM
  • 2、期望泛型
  • 3、“我们真的找到了吗?”
  • 5、当迁移到 TS…
  • 6、更多限制
  • 7、类型保护
  • 8、再谈泛型
  • 9、路由参数
  • 10、根据 API 响应创建 Interface
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档