返回介绍

十六、初学者的困惑

发布于 2024-09-07 18:09:17 字数 9003 浏览 0 评论 0 收藏 0

16.1 如何优雅的声明类型

16.1.1 基础

interface Basic {
  num: number;
  str: string | null;
  bol?: boolean;
}

五种 JS 值类型就声明好了。那数组、函数呢?

interface Func {
  func(str: string): void;
}

interface Arr {
  str: string[];
  mixed: Array<string | number>;
  fixedStructure: [string, number];
  basics: Basic[];
}

枚举类型也是很常用的,比如声明一个状态机的各个状态

enum Status {
  Draft,
  Published
}

// 也可指定值
enum Status {
  Draft = 'Draft',
  Published = 'Published'
}

16.1.2 糅合

独立声明

一个 ts 文件只声明一个类型或者接口,文件名为需要暴露的类型名称,方便检索和管理

就近声明

当一个声明没有被外部引用或者依赖时,可以考虑就近放在使用的地方,典型的场景是 React 组件的 PropsState 的类型声明

按职责分组

  • 在项目中,需要声明类型的可大致分为两类:一类是 model ,也就是接口请求相关的,包括入参和出参;另一类是 view ,界面渲染相关的。因此,我在 独立声明 的基础上,可以类型按照 modelview 的维度进行分组,相互独立。
  • 那么问题来了,如果是独立的类型声明的话,怎么把 model 的数据应用到 view 呢? 可能你需要一个 adapter 来做类型的的转换: DTOTypes -> adapter -> ViewTypes , 完成类似于将接口中的字符串映射成枚举类型这之类的转换

any

当遇到确实解决不了的类型报错的时候, as any 能带给你不一样的快感,但是不建议使用啊

16.2 如何引用外部库

JS 中, npm 上有丰富的海量的库帮我们完成日常的编码,可能并不是所有的库都能完全被应用到 TS 中,因为有些缺少类型声明

比如,在 TS 中使用 react , 你会得到这样的一个类型检查错误:

  • 因为 react 的库中并没有类型声明
  • 现在比较通用的做法是,实现和类型实现独立成两个库,也就是你需要再安装类型声明的库: @types/react
  • 当遇到上述问题的时候,尝试安装一下 @types/[package]
  • 然而,并不是所有的库都有类型声明的实现,也会有很多不支持 TS 的存在,然而又必须得使用这个库的时候该怎么办?

自己写声明

progressbar.js 为例,基本使用方法

import * as ProgressBar from 'progressbar.js';

new ProgressBar.Circle(this.$progress, {
  strokeWidth: 8,
  trailColor: '#e5e4e5',
  trailWidth: 8,
  easing: 'easeInOut'
});

我们需要对库中暴露出的 api 去做声明,对上述例子做个分解:暴露了 Circle 类,Circle 构造函数包含两个参数,一个 HTMLElement,一个 options. OK

// 首先声明一下模块:
declare module 'progressbar.js' {
  // 模块中暴露了 Circle 类
  export class Circle {
  constructor(container: HTMLElement, options: Options);
  }

  // 构造函数的 Options 需要单独声明 
  interface Options {
  easing?: string;
  strokeWidth?: number;
  trailColor?: string;
  trailWidth?: number;
  }
}

如此我们便完成了一个简单的声明,当然实际使用中的 API 肯定比上述情况复杂,根据使用情况,用了哪些 API 或者参数,就补充那些的声明即可

16.3 如何组织一个 TS 项目

  • TS 项目的目录组织上,跟 JS 项目一样,补充好 types 的声明就可以了
  • 需要注意的是,将你希望对外暴露的能力相关的类型声明都暴露出去,不友好的声明会让接入你项目的人非常的痛苦,同时,在 package.json 中需要指定 type 的 path, 比如:”types”: “dist/types/index.d.ts”
  • 另外,务必加上 tslint, 更规范的去用 TS 实现功能,对于入门而言尤为重要

16.4 TSX 和 JSX

  • 之前我们在用 JavaScriptReact 时,对文件的扩展名没有什么特别的要求, .js 或者 .jsx 都行。
  • 但在 TypeScript 中,如果你要使用 JSX 语法,就不能使用 .ts ,必须使用 .tsx 。如果你不知道,或者忘了这么做,那么你会在使用了 JSX 代码的地方收到类型报错,但代码本身怎么看都没有问题。这也是刚上手 TypeScript + React 时几乎每个人都会遇到的坑。
  • 关于这一点, TypeScript 只是在官方教程的示例代码中直接用了 *.tsx ,但并没有明确说明这一问题

16.5 变量的 Type 怎么找

  • 上手 TypeScript 之后很快我们就发现,即便是原生的 DOM 、或是 ReactAPI ,也经常会要我们手动指定类型。但这些结构并不是简单的 JavaScript 原始类型,在使用 JavaScript 编写相关代码时候由于没有这种需要,我们也没关心过这些东西的类型,突然问起来,还真不知道这些类型叫什么名字。
  • 不光是这些标准类型,同样的问题在很多第三方的库中也会遇到,比如一些组件库会检查你传入的 Props
  • 在我看来,这中间其实缺少了一部分的文档,来指导新用户如何找到所需要的类型。既然社区没有提供,那就我来吧。
  • 当然,让每个开发者都熟记所有的类型肯定是不现实的,总不能每接触一个新的库,就要去记一堆类型吧。放心,世界还是美好的,这种事情,当然是有方法的。
  • 最直白的方法就是去看库的 Types Definition ,也就是那些 .*d.ts 文件。如果你刚好有在用 VS Code 的话,有一个非常方便的操作:把鼠标移动到你想知道它类型的代码上(比如某个变量、某个函数调用,或是某个 JSX 标签、某个组件的 props),右键选择「Go to Definition」(或者光标选中后按 F12),就可以跳转到它的类型定义文件了。
  • 如果你更习惯使用 VS Code 之外的编辑器,我相信时至今日,它们应该也都早就对 TypeScript 提供了支持。具体操作我不太熟悉,你可以自己探索下(我一直用 VS Code,其它的不太熟)
  • 一般来说,这个操作可以直接把你带到你想要的地方,但考虑到类型是可以继承的,有时候一次跳转可能不太够,遇到这种情况,那就需要你随机应变一下,沿着继承关系多跳几次,直到找到你想要的内容。
  • 对于不熟悉的类型,可以通过这个方法去寻找,慢慢熟悉以后,你会发现,一些常见的类型还是很好找的,稍微联想一下英文的表达方式,配合自动补全的提示,一般都不难找到

16.6 常见 Types 之 DOM

  • TypeScript 自带了一些基本的类型定义,包括 ECMAScript 和 DOM 的类型定义,所有你需要的类型都可以从这里找到。如果你想做一些「纯 TypeScript 开发」的话,有这些就够了
  • 比如下面这张截图,就是对 <div> 标签的类型定义。我们可以看到,它继承了更加通用的 HTMLElement 类型,并且扩展了一个即将被废弃的 align 属性,以及两组 addEventListenerremoveEventListener ,注意这里使用了重载。

这里的命名也不是随便起的,都是在 MDN 上可以查到的。还是以 <div> 为例,我们已经知道它继承自 HTMLElement ,其实再往上, HTMLElement 继承自 ElementElement 又继承自 Node ,顺着这条路,你可以挖掘出所有 HTML 标签的类型

对于一些 DOM 相关的属性,比如 onclickonchange 等,你都可以如法炮制,找到它们的定义。

16.7 常见 Types 之 React

  • 关于 TypeScript 的问题,有不少其实是在使用第三方库的时候遇到的,React 就是其中比较典型的一个
  • 其实方法都一样,只不过相关的类型定义不在 TypeScript 中,而是在 @types/react 中。
  • React 的类型定义的名称其实也很直观,比如我们常见的 React.Component ,在定义 Class 组件时,我们需要对 PropsState 预先进行类型定义,为什么呢?答案就在它的类型定义中

  • 再比如,当我们在写一些组件时,我们可能会需要向下传递 this.props.children ,但 children 并没有被设为默认值,需要我们自己定义到 props 上,那么它的类型应该是什么呢
  • 到类型定义中搜一下关键字 children ,很快我们就找到了下面的定义

所有 ReactJSX 所代表的内容,无论是 render() 的返回,还是 children ,我们都可以定义为一个 ReactNode 。那这个 ReactNode 长什么样呢?我们通过右键继续寻找

看到这里,我们不光找到了我们想要的类型,还顺带明白了为什么 render() 可以返回 booleannullundefined 表示不渲染任何内容。 那么事件呢?当我们给组件定义事件处理函数的时候,也经常会被要求指定类型。还是老办法,找不到咱就搜,比如 onClick 不清楚,那我们就以它为关键字去搜

据此我们找到一个叫 MouseEventHandler 的定义,这名字,够直白吧。好了,我们找到想要的了。不过既然来了,不如继续看一下,看看还能发现什么。我们右键 MouseEventHandler 急需往下看:

看到了吗,所有的事件处理函数都有对应的定义,每个都需要一个泛型参数,传递了事件的类型,名称也挺直白的

事件的类型也被我们挖出来了,以后如果需要单独定义一个事件相关的类型,就可以直接用了。以此类推,不管是什么东西的类型,都可以去它们对应的 @types/xxx 里,按关键字搜

16.8 多重 extends

  • 我们知道 Interface 是可以多继承的, extends 后面可以跟多个其它 Interface ,我们不能保证被继承的多个 Interface 一定没有重复的属性,那么当属性重复,但类型定义不同时,最终的结果会怎么样呢?
  • TypeScript 中, Interface 会按照从右往左的顺序去合并多个被继承的 Interface ,也就是说,同名属性,左边的会覆盖右边的
interface A {
  value?: string
}
interface B {
  value: string
}
interface C {
  value: number
}
interface D extends A, B {}// value?: string
interface E extends B, C {}// value: string

16.9 obj[prop] 无法访问怎么办

  • 有时候我们会定义一些集合型的数据,例如对象、枚举等,但在调用的时候,我们未必会直接通过 obj.prop 的形式去调用,可能会是以 obj[prop] 这种动态索引的形式去访问,但通过动态索引的方式就无法确定最终访问的元素是否存在,因此在 TypeScript 中,默认是不允许这种操作的
  • 但这又是个非常合理,而且非常常见的场景,怎么办呢? TypeScript 允许为类型添加索引,以实现这一点。
interface Foo {
  x: string,
  y: number
  [index: string]: string | number
}
  • 这个方法虽然有效,但每次都要手动为类型加索引,重复多了也挺心累的。包括在一些「配置对象」中,我们甚至无法确定有哪些类型,有没有一种更加通用、更加一劳永逸的方法。
  • 其实在 TypeScript 的官方文档中就有提到这个方案,官方管它叫 OptionBag ,大概就是指 config 、o ption 等用于提供配置信息的这么一类参数。我不是很确定这到底是个常规的英文单词,还是 TypeScript 中特定的术语(个人感觉是前者),反正就这么个意思吧。 简单说来,我们可以定义下面这样一个类型:
interface OptionBag {
  [index: string]: any
}
  • 这是一个非常通用的结构,以字符串为键,值可以是任何类型,并且支持索引 —— 这不就是 Object 么。
  • 之后所有需要动态索引的结构,或是作为配置对象的结构,都可以直接指定为,或是继承 OptionBag 。这个方案以牺牲一定的类型检查为代价,换取了操作上的便利。
  • 理论上讲, OptionBag 可以适用于所有类似对象这样的结构,但不建议各位真就这么做。这个方案只能是用在一些对类型要求不那么严格,或是无法预知类型的场景中,能够确定的类型还是尽可能地写一下,否则就失去了使用 TypeScript 意义了

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文