d.ts 文件的妙用
默认情况下
*.d.ts 中的 type,interface 全局生效
// types.d.ts
type User = {
name: string;
age: number;
}
// main.ts
type A = User
但如果 *.d.ts 里有 import 或 export,则 User 只在当前模块生效
// types.d.ts
export type User = {
name: string;
age: number;
}
// main.ts
type A = User // TODO: 报错
但是加了import 或 export,还想全局生效怎么做?用 declare global
// types.d.ts
import x from 'vite'
type Local = string; // TODO: 注意这里不是全局的
declare global {
interface User {
name: string;
age: number;
}
}
d.ts 与 JS 文件结合
假设你已经有如下 JS 文件
// x.js
window.addOne = function (n) {
return n + 1
}
现在你想在你的 TS 文件中直接使用,虽然功能上可以,但是会有类型报错
// main.ts
const result = addOne(1) // TODO: 报错,会提示你没有这个函数
所以我们需要声明一下
// global.d.ts
const addOne: (n: number) => number
// 等价于
type AddOne = (n: number) => number // 类型声明
const addOne:AddOne // 变量声明
这样的弊端就在,如果你这里的类型写错了,TS 也不能帮你检测出来
但是实际场景,已经没有人写这样的代码了,实际我们会遇到这样的 JS 文件
// x.js
const add = (a, b) => a + b
export { add }
如果我们在 TS 文件中直接使用,会提示你这是一个 JS 模块,没有类型,TS 不能直接使用
import { add } from './x'
所以我们一般会直接在同级目录下新建相同名字的 *.d.ts
// x.d.ts
const add: (a: number, b: number) => number
export { add }
这样 TS 就能帮你自动建立关联
d.ts 与 浏览器
当我们有如下代码的时候,这里的类型哪里来的呢?
const app = document.getElementById('app') // TODO: 这里的 app 直接就有类型了
是 tsconfig 里面加载好了的
// tsconfig.json
{
"compilerOptions": {
"lib": ["ESNext", "DOM"] // 这里的 DOM
}
}
那这个 DOM 相关的文件哪里来的呢?
是社区的程序员们一行一行写好的,然后要么通过 github 上下载下来的,或从 npm 下载下来的,这样我们才能很舒服的写代码,如果没有这些文件,最基础的 JS 代码都没有类型提示
*.d.ts + JS + React
正常我们是无法在 TS 中使用 React 的,因为 React 源码是用的 flow,而不是 TS
import React from 'react' // TODO: 报错
但是我们可以通过 npm add -D @types/react
,就能解决这个问题
所以说如果 node_modules/xxx 如果没有提供类型声明,就会去 node_modules/@types/xxx 去寻找
这也是 React 团队的解决思路,但是比不上 Vue 团队的方案好
*.d.ts + JS + Vue3
我们下载完后发现可以直接使用
import { createApp } from 'vue'
通过看包的源码得:
原来是 vue 的 package.json 声明了 types 属性,所以我们就不用额外下载关联了
小结
- *.d.ts 的来历
- 源码是 JS/Flow
- 手写 *.d.ts
- 源码是 TS
- 自动生成 *.d.ts
- tsc -d x.ts
- tsconfig
- 源码是 JS/Flow
*.d.ts 与 Node.js
import * as fs from 'fs' // TODO: 报错
Node 也是一样,需要安装 @types/node,和 React 一样,为什么还要单独讲呢?我们看一下源码
可以看到里面有一堆的 reference,这些是干嘛用的呢?
- reference lib -- 引入一个 lib(有点像 tsconfig 里面的 lib 选项)
- reference path -- 引入一个相对路径的文件
- reference types="node" -- 引入一个 types(有点像 tsconfig 里面的 types 选项)
Node 为什么要用这么奇怪的语法呢?
因为早些年 TS 出生的时候,那个时候还没有 ES modules,所以 TS 不得不发明一种模块引入语法
如果你的 tsconfig 里 types里没有没有包涵你想使用的类型,如:node,你可以这样手动添加
/// <reference types="node"
import * as fs from 'fs'
总结
给 JS 添加类型声明的方式
- 用 global.d.ts 声明全局变量
- 用 types.d.ts 声明全局类型
- 如果 import / export 了,就用 declare global
- 用 同名.d.ts 声明类型模块
- 用 @types/xxx 给 xxx 添加类型声明