模块化开发

TypeScript支持两种方式来控制我们的作用域:

  • 模块化:每个文件可以是一个独立的模块,支持 ES Module,也支持 CommonJS;
  • 命名空间:通过 namespace 来声明一个命名空间

命名空间在 TypeScript 早期时,称之为内部模块,主要目的是将一个模块内部再进行作用域的划分,防止一些命名冲突的问题。
ts 发布是 2014 年,那个时候还没有发布 ES Module,现在我们已经可以使用 ES Module 解决作用域问题了,所以命名空间现在算是一个历史遗留问题。不建议继续使用。

  1. // 命名空间 time
  2. export namespace time {
  3. // 命名空间内的东西要被外界拿到都要 export 出去
  4. export function format(time: string) {
  5. return "2222-02-22"
  6. }
  7. export function foo() {
  8. }
  9. export let name: string = "abc"
  10. }
  11. export namespace price {
  12. export function format(price: number) {
  13. return "99.99"
  14. }
  15. }
  16. // 通过命名空间拿到函数
  17. time.format("hhh")
  18. price.format(123)

类型声明

类型查找

之前我们所有的 typescript 中的类型,几乎都是我们自己编写的,但是我们也有用到一些其他的类型,如:

  1. 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 的环境中会带有的;

这些库通常有两种类型声明方式:
方式一:库作者在自己库中进行类型声明(编写.d.ts文件),比如 axios
方式二:通过社区的一个公有库 DefinitelyTyped ,这里面存放了一些别人写好的类型声明文件,

什么情况下需要自己来定义声明文件呢?
情况一:我们使用的第三方库是一个纯的 JavaScript 库,没有对应的声明文件;比如 lodash
情况二:我们给自己的代码中声明一些类型,方便在其他地方直接进行使用;

编写自定义声明

用到啥,就声明啥

  1. 声明一些变量、函数、类
  2. 声明模块:
    • 声明模块的语法:declare module '模块名' { export xxx }
    • 在声明模块的内部,我们可以通过 export 导出库的类、函数等;
  3. 声明文件:在某些情况下,我们也可以声明文件:
    • 比如在开发 vue 的过程中,默认是不识别我们的.vue文件的,那么我们就需要对其进行文件的声明;
    • 比如在开发中我们使用了 jpg 这类图片文件,默认 typescript 也是不支持的,也需要对其进行声明;
    • 在 vue-cli 中会自动生成类型声明文件shims-vue.d.ts,里面自动声明了.vue文件为模块。
  4. 声明命名空间
    • 比如我们在 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 方法了 }

  1. <a name="TvkPz"></a>
  2. # shims-vue.d.ts 文件
  3. `.vue`文件 ts 也是不认识的,所以需要手动声明为模块。在 vue-cli 中会自动生成类型声明文件`shims-vue.d.ts`,里面自动声明了`.vue`文件为模块,并且将 vue 文件都定义为一个 DefineComponent 实例导出,以做到导出的类型限制。
  4. ```javascript
  5. // 声明 .vue 文件为模块
  6. declare module "*.vue" {
  7. // DefineComponent 是一个组件 class 类的类型
  8. import type { DefineComponent } from "vue";
  9. // 相当于 Java 中 DefineComponent component = new DefineComponent
  10. // 所以 component 是个 DefineComponent 类型的实例
  11. const component: DefineComponent<{}, {}, any>;
  12. // 将 .vue 文件都视为一个组件实例导出,并且实例类型为 DefineComponent
  13. export default component;
  14. }

之前 vue 文件的导出就是直接导出没有限制,但是在 ts 中导出需要经过 defineComponent 函数处理后才能导出。

  1. <template> </template>
  2. <script>
  3. // 直接导出,没有任何限制
  4. export default {
  5. // 这里可以随意导出
  6. }
  7. </script>
  1. <template> </template>
  2. <script lang="ts">
  3. import { defineComponent } from "vue";
  4. export default defineComponent({
  5. // defineComponent 函数对导出内容进行类型限制,不能随便导出
  6. // 这是 vue 提供的方法,该方法在 js 中不会起作用,就像 function(obj) { return obj }
  7. // 但是在 ts 中,这个方法就会对内容进行类型限制
  8. });
  9. </script>

tsconfig.json 文件

tsconfig.json是用于配置TypeScript编译时的配置选项:

我们这里讲解几个比较常见的:
image.png
image.png