本文使用的 TypeScript 版本为 5.3.2。示例代码已上传到 GitHub (opens new window)。
在 TypeScript 项目中难免会用到一些 JS 库,TypeScript 官方支持使用 @types/
的同名库来作为 JS 库的类型定义。如果 @types/
里对应的库不存在,或者类型不全,开发者仍然需要自行补充类型。
# 向 DefinitelyTyped 提 PR
如果 @types/
里对应的库不存在,或者类型不全,可以直接向 DefinitelyTyped/DefinitelyTyped (opens new window) 提 PR,创建或者更新对应的库。
DefinitelyTyped 虽然是一个非常庞大的 monorepo,但是有很完善的贡献代码和 review 的流程:首先会 request 包作者的 review,如果包作者没有 review,会邀请项目的维护者来 review。提完 PR 以后大概一个月之内就能合并,合并之后等待发版,安装新的 @types/
就能用上我们贡献的类型了,不需要在我们的项目中做更多的配置。
当然,并不是所有场景都适合向 DefinitelyTyped 提 PR。比如:
- 项目需要立即使用这些类型,等待 PR 合并的流程太过漫长
- 项目只用到了 JS 库的一小部分代码,为 JS 库创建一个完整的
@types
库会耗费过多精力
在这些情况下,我们仍然可以在自己的项目中为 JS 库中补充类型。不过大部分博客都没有讲到如何在本地库添加类型、补充类型,这也是本文的目的所在。
# 为 JS 库扩展类型
如果你使用的库具有 @types
库,但是库不全,你可以为其补充类型。
有下面一段 JS 代码,可以正常运行,但缺少 BMapGL.DistrictLayer
类和 BMapGL.Map.addDistrictLayer()
BMapGL.Map.removeDistrictLayer()
方法的类型。
// https://github.com/lyh543-lab/typescript-custom-typing-example/blob/main/src/App.tsx#L10
function CustomDistrictLayer() {
const {map} = useMapContext(); // map 的类型是 BMapGL.Map
useEffect(() => {
if (!map) {
return;
}
const districtLayer = new BMapGL.DistrictLayer({
name: ['北京市'],
});
map.addDistrictLayer(districtLayer);
return () => map.removeDistrictLayer(districtLayer);
}, [map]);
return null;
}
我们可以直接新建 src/global.d.ts
(或者别的 src/**/*.d.ts
),写入以下内容:
/// <reference types="react-bmapgl/types/bmapgl" />
declare namespace BMapGL {
/**
* @link https://lbsyun.baidu.com/index.php?title=district
*/
export class DistrictLayer {
constructor(options: {
name: string[];
kind?: number;
strokeColor?: string;
strokeOpacity?: number;
fillColor?: string;
});
}
export interface Map {
addDistrictLayer(layer: DistrictLayer): void;
removeDistrictLayer(layer: DistrictLayer): void;
}
}
/// <reference types="react-bmapgl/types/bmapgl" />
可以理解成 .d.ts
之间的 import
,不过 import
更倾向于执行包里的声明语句然后引入一部分命名,<reference />
更倾向于引入一个库并追加更多的类型。
这样可以在 BMapGL 下新定义 DistrictLayer
类,然后为 Map
类拓展两个新的方法。新建完跑一下 pnpm run tsc
就可以确认没问题了。
# 为 JS 库添加类型
如果你使用的库是纯 JS 库,也没有一点 TS 定义,你可以选择创建一个新的目录,作为这个库的类型定义。
下面的例子中,mapvgl
就是一个纯 JS 库。
// https://github.com/lyh543-lab/typescript-custom-typing-example/blob/main/src/App.tsx#L26
import * as mapvgl from 'mapvgl';
function CustomMapVGLLayer() {
const {map} = useMapContext();
useEffect(() => {
if (!map) {
return;
}
const view = new mapvgl.View({map});
const layer = new mapvgl.PointLayer({
color: '#E91E63',
shape: 'circle', // 默认为圆形,可传square改为正方形
blend: 'lighter',
size: 20,
data: [{
geometry: {
type: 'Point',
coordinates: [116.402544, 39.928216]
}
}]
});
view.addLayer(layer);
return () => view.destroy();
}, [map]);
return null;
}
为此,可以创建一个 src/@types/mapvgl/index.d.ts
(或者别的 src/**/mapvgl/index.d.ts
),定义自己项目里需要的那些 API:
// https://github.com/lyh543-lab/typescript-custom-typing-example/blob/main/src/%40types/mapvgl/index.d.ts
/// <reference types="react-bmapgl/types/bmapgl" />
// docs: https://mapv.baidu.com/gl/docs/index.html
// You can send docs into ChatGPT to generate typescript definitions
/* eslint-disable @typescript-eslint/no-explicit-any */
export interface ViewOptions {
map: BMapGL.Map;
mapType?: 'bmap' | 'blank' | 'cesium';
effects?: Array<any>; // The specific effect type can replace 'any' if available
}
/**
* @link https://mapv.baidu.com/gl/docs/View.html
*/
export class View {
constructor(options: ViewOptions);
addLayer(layer: Layer): void;
removeLayer(layer: Layer): void;
removeAllLayers(): void;
getAllLayers(): Layer[];
getAllThreeLayers(): ThreeLayer[];
hide(): void;
show(): void;
hideLayer(layer: Layer): void;
showLayer(layer: Layer): void;
destroy(): void;
}
// ...
定义完以后跑 pnpm run tsc
,会发现仍然会报错,tsc 提示不存在 @types/mapvgl
这个库。我们虽然定义了 ViewOptions
接口和 View
类,但 tsc 并不知道我们定义的是 mapvgl
库里的 ViewOptions
和 View
。所以需要在 tsconfig.json
里关联一下:
// https://github.com/lyh543-lab/typescript-custom-typing-example/blob/main/tsconfig.json
{
"compilerOptions": {
// works on TypeScript 4.7+
"paths": {
"mapvgl": ["./src/@types/mapvgl/index.d.ts"],
},
// works on TypeScript 4.6
// on TypeScript 4.7+, typeRoots seems not working without defining paths
"typeRoots": [ "./src/types", "./node_modules/@types"],
}
}
再跑 tsc 就没问题啦。