模块化开发
TypeScript支持两种方式来控制我们的作用域:
- 模块化:每个文件可以是一个独立的模块,支持 ES Module,也支持 CommonJS;
- 命名空间:通过 namespace 来声明一个命名空间
命名空间在 TypeScript 早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题。
ts 发布是 2014 年,那个时候还没有发布 ES Module,现在我们已经可以使用 ES Module 解决作用域问题了,所以命名空间现在算是一个历史遗留问题。不建议继续使用。
// 命名空间 time
export namespace time {
// 命名空间内的东西要被外界拿到都要 export 出去
export function format(time: string) {
return "2222-02-22"
}
export function foo() {
}
export let name: string = "abc"
}
export namespace price {
export function format(price: number) {
return "99.99"
}
}
// 通过命名空间拿到函数
time.format("hhh")
price.format(123)
类型声明
类型查找
之前我们所有的 typescript 中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型,如:
const imageEl = document.querySelector("#image") as HTMLImageElement
大家是否会奇怪,HTMLImageElement 类型来自哪里呢?
其实这里就涉及到 typescript 对类型的管理和查找规则了,在 ts 中所有的类型都必须声明后才能使用。
那么 typescript 会在哪里查找我们的类型声明呢?
- 内置类型声明;
- 外部定义类型声明;
- 自己定义类型声明;
typescript 的声明文件:.d.ts
文件
- 我们之前编写的 typescript 文件都是
.ts
文件,这些文件最终会输出.js
文件,也是我们通常编写代码的地方; 还有另外一种文件
.d.ts
文件,它是用来做类型的声明(declare)。 声明后就可以做类型检测,告知 typescript我们有哪些类型;内置类型声明
内置类型声明是 typescript 自带的、帮助我们内置了 JavaScript 运行时的一些标准化 API 的声明文件;
包括比如 Math、Date 等内置类型,也包括 DOM API,比如 Window、Document 等;
内置类型声明通常在我们安装 typescript 的环境中会带有的;
- https://github.com/microsoft/TypeScript/tree/main/lib
外部定义类型声明和自定义声明
外部类型声明通常是我们使用一些库(比如第三方库)时,需要的一些类型声明。
这些库通常有两种类型声明方式:
方式一:库作者在自己库中进行类型声明(编写.d.ts文件),比如 axios
方式二:通过社区的一个公有库 DefinitelyTyped ,这里面存放了一些别人写好的类型声明文件,
- 该库的GitHub地址:https://github.com/DefinitelyTyped/DefinitelyTyped/
- 查找该库中有哪些写好的声明:https://www.typescriptlang.org/dt/search?search=
- 比如我们安装 react 的类型声明: npm i @types/react —save-dev
什么情况下需要自己来定义声明文件呢?
情况一:我们使用的第三方库是一个纯的 JavaScript 库,没有对应的声明文件;比如 lodash
情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;
编写自定义声明
用到啥,就声明啥
- 声明一些变量、函数、类
- 声明模块:
- 声明模块的语法:
declare module '模块名' { export xxx }
- 在声明模块的内部,我们可以通过 export 导出库的类、函数等;
- 声明模块的语法:
- 声明文件:在某些情况下,我们也可以声明文件:
- 比如在开发 vue 的过程中,默认是不识别我们的
.vue
文件的,那么我们就需要对其进行文件的声明; - 比如在开发中我们使用了 jpg 这类图片文件,默认 typescript 也是不支持的,也需要对其进行声明;
- 在 vue-cli 中会自动生成类型声明文件
shims-vue.d.ts
,里面自动声明了.vue
文件为模块。
- 比如在开发 vue 的过程中,默认是不识别我们的
- 声明命名空间
- 比如我们在 index.html 中直接引入了 jQuery:
- 我们可以进行命名空间的声明,就能使用 $ 符号了。当然也可以使用声明模块的方式,毕竟命名空间也是模块化的一种方式。 ```javascript // 声明模块 declare module ‘lodash’ { export function join(arr: any[]): void // 声明 join 方法 }
// 声明变量/函数/类 declare let whyName: string declare let whyAge: number declare let whyHeight: number
declare function whyFoo(): void
declare class Person { name: string age: number constructor(name: string, age: number) }
// 声明.vue文件 declare module “*.vue” { import type { DefineComponent } from “vue”; const component: DefineComponent<{}, {}, any>; export default component; }
declare module ‘.jpg’ { const src: string export default src } declare module ‘.jpeg’ declare module ‘.png’ declare module ‘.svg’ declare module ‘*.gif’
// 声明命名空间 declare namespace $ { export function ajax(settings: any): any // 可以在 ts 中使用 $.ajax 方法了 }
<a name="TvkPz"></a>
# shims-vue.d.ts 文件
`.vue`文件 ts 也是不认识的,所以需要手动声明为模块。在 vue-cli 中会自动生成类型声明文件`shims-vue.d.ts`,里面自动声明了`.vue`文件为模块,并且将 vue 文件都定义为一个 DefineComponent 实例导出,以做到导出的类型限制。
```javascript
// 声明 .vue 文件为模块
declare module "*.vue" {
// DefineComponent 是一个组件 class 类的类型
import type { DefineComponent } from "vue";
// 相当于 Java 中 DefineComponent component = new DefineComponent
// 所以 component 是个 DefineComponent 类型的实例
const component: DefineComponent<{}, {}, any>;
// 将 .vue 文件都视为一个组件实例导出,并且实例类型为 DefineComponent
export default component;
}
之前 vue 文件的导出就是直接导出没有限制,但是在 ts 中导出需要经过 defineComponent 函数处理后才能导出。
<template> </template>
<script>
// 直接导出,没有任何限制
export default {
// 这里可以随意导出
}
</script>
<template> </template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
// defineComponent 函数对导出内容进行类型限制,不能随便导出
// 这是 vue 提供的方法,该方法在 js 中不会起作用,就像 function(obj) { return obj }
// 但是在 ts 中,这个方法就会对内容进行类型限制
});
</script>
tsconfig.json 文件
tsconfig.json是用于配置TypeScript编译时的配置选项:
我们这里讲解几个比较常见的: