7

小记 TypeScript 中的循环引用问题

 3 years ago
source link: https://blog.csdn.net/tkokof1/article/details/108984865
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

小记 TypeScript 中的循环引用问题

随着项目规模的不断增长,循环引用问题似乎总是不可避免,本文就 TypeScript 中可能出现的循环引用问题做了一些简单记录~

平时编写 TypeScript 代码时,一般都倾向于使用模块(Module),通过结合使用 import 和 export 我们便可以方便的进行模块的导入和导出.

举个简单的例子,假设我们有以下的 TypeScript 代码文件(A.ts):

export class A {
    // methods here
}

可以看到,上述代码使用 export 导出了类型 A,如果我们需要在另外的 TypeScript 代码文件(B.ts)中使用类型 A,我们可以直接使用 import :

import { A } from "./A.ts"

class B {
    // use A here
}

接着我们让代码变的复杂一些,假设现在类型 A 也要使用类型 B 了,那么相关的代码可能会变成这样:

import { B } from "./B.ts"

export class A {
    // use B here
}
import { A } from "./A.ts"

export class B {
    // use A here
}

此时,类型 A 与 类型 B 便产生了循环引用,一般来讲是应该尽量避免的,但是在较大型的项目中往往又很难规避,所以我们需要一种可以处理循环引用问题的方法(之前关于这个话题自己也写过一篇博文),而实际上,TypeScript 中的 import 和 export 是可以处理循环引用的:

当 import 遇到导入完毕或者说正在导入的模块(文件)时,是直接返回导入结果的(尽管这个结果可能是不完整的),而不是递归的进行模块的导入操作,还是拿上面的代码举例,假设我们首先导入 A 模块:

  • A 模块尝试导入 B 模块
  • 由于 B 模块尚未导入,程序开始导入 B 模块
  • B 模块尝试导入 A 模块
  • 由于 A 模块正在导入,所以程序直接返回当前导入结果(尽管当前结果是不完整的)
  • 将类型 B 加入到 B 模块的导出数据中(export class B)
  • B 模块导入完成,继续 A 模块的导入
  • 将类型 A 加入到 A 模块的导出数据中(export class A)
  • A 模块导入完成

值得注意的是,上述的这种循环引用处理方式是不完备的,该方式并不能正确处理更复杂一些的循环引用情况(主要是在一些需要及时访问模块导出数据的情况下,譬如类继承(extends),静态引用等等)

考虑下面的循环引用情况:

import { C } from "./C.ts"

export class A {
    // use C here
}
import { A } from "./A.ts"

export class B extends A {
    // methods here
}
import { B } from "./B.ts"

export class C extends B {
    // methods here
}

假设我们首先导入 A.ts,我们来分析下导入流程:

  • A 模块尝试导入 C 模块
  • 由于 C 模块尚未导入,所以我们开始导入 C 模块
  • C 模块尝试导入 B 模块
  • 由于 B 模块尚未导入,所以我们开始导入 B 模块
  • B 模块尝试导入 A 模块
  • 由于 A 模块正在导入,所以程序直接返回当前导入结果
  • B 模块继承 A 模块,尝试在当前(A 模块)导入结果中访问类型 A 的定义
  • 但是当前(A 模块)导入结果中并没有类型 A 的定义(因为当前 A 模块的导入还没有进行到 export class A)
  • Ops,导入出错(找不到类型 A 的定义) …

对于上面这种情况,其实有一个技巧可以解决上面的问题:在不需要及时访问模块导出数据的情况下,我们可以将模块的导入操作后置.

就上面的例子来讲,我们可以这么修改代码:

export class A {
    // use C here
}

// put import after export
import { C } from "./C.ts"
import { A } from "./A.ts"

export class B extends A {
    // methods here
}
import { B } from "./B.ts"

export class C extends B {
    // methods here
}

我们再来分析下上面代码的导入流程(仍然假设首先导入 A.ts):

  • A 模块将类型 A 加入到 A 模块的导出数据中(export class A)
  • A 模块尝试导入 C 模块
  • 由于 C 模块尚未导入,所以我们开始导入 C 模块
  • C 模块尝试导入 B 模块
  • 由于 B 模块尚未导入,所以我们开始导入 B 模块
  • B 模块尝试导入 A 模块
  • 由于 A 模块正在导入,所以程序直接返回当前导入结果
  • 类型 B 继承 类型 A ,尝试在当前(A 模块)导入结果中访问类型 A 的定义
  • 当前(A 模块)导入结果中存在类型 A 的定义, 类型 B 可以正常定义导出
  • B 模块将类型 B 加入到 B 模块的导出数据中(export class B)
  • B 模块导入完成,继续 C 模块的导入
  • 类型 C 继承 类型 B,尝试在当前(B 模块)导入结果中访问类型 B 的定义
  • 当前(B 模块)导入结果中存在类型 B 的定义, 类型 C 可以正常定义导出
  • C 模块导入完成, 继续 A 模块的导入
  • A 模块导入完成

但是如果我们尝试首先导入 B 模块(B.ts)的话,仍然会遇到导入出错的问题:

  • B 模块尝试导入 A 模块
  • 由于 A 模块尚未导入,所以我们开始导入 A 模块
  • A 模块尝试导入 C 模块
  • 由于 C 模块尚未导入,所以我们开始导入 C 模块
  • C 模块尝试导入 B 模块
  • 由于 B 模块正在导入,所以程序直接返回当前导入结果
  • 类型 C 继承 类型 B,尝试在当前(B 模块)导入结果中访问类型 B 的定义
  • 但是当前(B 模块)导入结果中并没有类型 B 的定义(因为当前 B 模块的导入还没有进行到 export class B)
  • Ops,导入出错(找不到类型 B 的定义) …

这种情况下,我们已经不能通过后置 import 来解决问题了(因为类型 B 和 类型 C 的定义导出都需要及时访问导入模块的导出数据),我们只能通过改变模块的导入顺序来规避导入出错的问题 …


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK