TypeScript 类型体操 - 实践

2023-05-17 17:03:21 浏览数 (1)

# 类型声明和模块

# TS 类型声明的三种来源

TypeScript 设计了 declare 的语法,可以单独声明变量的类型:

代码语言:javascript复制
interface Person {
  name: string;
  age: number;
}

declare const developer: Person;

declare function add(a: number, b: number): number;

单独声明了类型,使用这些 api 的时候也就能做类型检查。

像 JS 引擎那些 api,还有浏览器提供的 api,这些基本是必用的,而且都有标准的。所以 TypeScript 给内置了它们的类型声明。

TypeScript 包下有个 lib 目录,里面有一堆 lib.xx.d.ts 的类型声明文件,这就是 TS 内置的一些类型声明。因为这些只是声明类型,而没有具体的 JS 实现,TS 就给单独设计了一种文件类型,也就是 d.tsddeclare 的意思。

可以在 tsconfig.json 中配置 lib 属性,来指定使用哪些内置的类型声明:

代码语言:javascript复制
{
  "compilerOptions": {
    "lib": ["dom", "es2015"]
  }
}

内置的类型声明只有 domes 的。dom 是浏览器的,es 是 JS 标准的。其余的环境的 api 可能没有标准,经常变,所以没有内置。

node 等环境的 api 因为没有标准而没有被 TS 内置,但 TS 同样也支持了这些环境的类型声明的配置。

但是可以通过 npm 安装第三方的类型声明,比如 @types/node,这样就可以使用 node 的 api 了。

代码语言:javascript复制
{
  "compilerOptions": {
    "types": ["node"],
    "typeRoots": ["./node_modules/@types", "./types"]
  }
}

TS 会先加载内置的 lib 的类型声明,然后再去查找 @types 包下的类型声明。

除了给 node 等环境的 api 加上类型声明外,@types 包还有一种用途,就是给一些 JS 的包加上类型声明:

如果代码本身是用 ts 写的,那编译的时候就可以开启 compilerOptions.declaration,来生成 d.ts 文件:

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

然后在 package.json 里配置 types 来指定 dts 的位置, 这样就不需要单独的@types 包:

代码语言:javascript复制
{
  "types": "dist/index.d.ts"
}

如果代码不是用 ts 写的,那可能既需要单独写一个 @types/xxx 的包来声明 ts 类型,然后在 tsconfig.json 里配置下,加载进来。

对于自己写的 ts 代码,配置下编译的入口文件,通过 includes 指定一堆,然后通过 excludes 去掉一部分。还可以通过 files 再单独包含一些:

代码语言:javascript复制
{
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"],
  "files": ["src/index.ts"]
}

tsc 在编译的时候,会分别加载 lib 的,@types 下的,还有 includefiles 的文件,进行类型检查。

# 全局类型声明 vs 模块类型声明

TS 最早支持的模块化方案是 namespace

代码语言:javascript复制
namespace A {
  export interface Person {
    name: string;
    age: number;
  }

  export const a = 1;

  export function add(a: number, b: number) {
    return a   b;
  }
}

// "use strict";
// var A;
// (function (A) {
//     A.a = 1;
//     function add(a, b) {
//         return a   b;
//     }
//     A.add = add;
// })(A || (A = {}));

namespace 编译后,在全局上放一个对象,然后对象上再挂几个暴露出去的属性。

后来,出现了 CommonJS 的规范,那种不能叫 namespace 了,所以 TS 支持了 module@types/node 的 api 定义就是一堆的 module

`module` 和 `namespace` 的区别

没什么区别,只不过 module 后一般接一个路径,而 namespace 后一般是一个命名空间名字。其他的语法都一样的。

再后来,JS 有了 es module 规范,所以现在推荐直接用 import export 的方式来声明模块和导入导出了。

额外多了 import type 的语法,可以单独引入类型:

代码语言:javascript复制
import type { Person } from "./types";

现在声明模块不推荐用 namespacemodule,尽量用 es module

有了 es module 之后,TS 有了一个单独的设计:dts 中,如果没有 importexport 语法,那所有的类型声明都是全局的,否则是模块内的

在存在 import 时需要通过手动 declare global 来声明全局的类型:

代码语言:javascript复制
declare global {
  interface Person {
    name: string;
    age: number;
  }
}

还可以使用 reference 来引入其他的 d.ts 文件同时,也可以声明全局的类型:

代码语言:javascript复制
/// <reference path="./types.d.ts" />

# 使用 Project References 优化编译性能

如果项目下有一些相对独立的模块,别的模块的变动不影响它,但是它却要跟着重新编译一次,这时就可以用 Project Reference 来优化。

在独立的模块下添加 tsconfig.json,加上 composite 的编译选项,在入口的 tsconfig.json 里配置 references 引用这些独立的模块。然后执行 tsc --build 或者 tsc -b 来编译。

0 人点赞