【问题标题】:How to properly import/export classes across angular modules?如何跨角度模块正确导入/导出类?
【发布时间】:2018-08-10 03:20:09
【问题描述】:

这个问题来自企业应用的上下文。

从我读过的所有书籍和网上看到的关于 Angular 应用程序的示例中,每次我们创建一个类(组件、服务、实体等)时,我们都会在类型定义中导出它们,然后直接导入它们无论两个类属于相同还是不同的角度模块,我们都需要引用(类似于在 C# 上使用命名空间)。

例如:

// In 'Commons/logger.service.ts'
export class LoggerService { ... }

// In 'Core/common.service.ts'
export class CommonService { ... }

// In 'Products/' module
import { LoggerService } from '../Commons/logger.service'
import { CommonService } from '../Core/common.service'

export class ProductComponent { ... }

我开始从事一个(大型)企业应用程序项目,并注意到一种我以前从未见过的方法,他们创建文件来收集每种类型的类(服务、实体、方法参数、组件),然后导出它们中的每一个,导出这些文件中的每一个都在其相应的角度模块文件中,然后,不是直接从类型文件导入类型,而是从给定模块执行导入。

前面的例子会变成这样:

// In 'Commons/logger.service.ts'
export class LoggerService { ... }

// In 'Commons/services.ts'
export * from './logger.service.ts'
export * from './other.service.ts'
export * from './another.service.ts'

// In 'Commons/commons.module.ts'
export * from 'services.ts'
export * from 'entities.ts'
/* ... */


// In 'Products/' module
import { LoggerService, OtherService } from '../Commons/commons.module'

export class ProductComponent { ... }

鉴于这种方法比以前的方法更冗长,并且在某些情况下会引发尴尬的结果(如果在同一模块中导入类,则会出现循环引用警告)

我的问题是:

  1. 从良好设计或最佳实践的角度推荐哪种方法?
  2. 是否建议使用这种方法而不是前者?为什么?适用于哪些情况?
  3. 为什么在主要的文档来源(角度在线文档、角度书籍等)中没有引入这种方法?
  4. 这种方法的优缺点是什么。

【问题讨论】:

  • 我猜你有循环依赖时,这种方法可以让你正确控制模块的加载顺序
  • 我认为您可能想研究在您的应用程序中使用具有导入和导出功能的共享模块。然后可以根据需要将此共享模块导入其他模块。这是一些关于它的 Angular 文档:angular.io/guide/sharing-ngmodules
  • @NicholasPesa:这种方法非常适合收集常见的导入以减少重复。我在这里谈论的是我公司的内部指南,其中每当您需要一个类型时,而不是直接从其文件中导入它,您必须从其模块中导入它,同样每当您导出新类型时,您需要遵循示例中提到的所有“导出层次结构”。就我个人而言,我认为这是为了解决最初可能不存在的问题而进行的过度设计。这就是为什么我正在寻找一些专家对该主题的意见。
  • @mdarefull 我明白你现在在说什么了。我的错误是我误读了中间部分解释你的最终目标。
  • 我不知道答案......但我想知道当你总是需要所有东西时,“摇树”会做什么?

标签: angular typescript ecmascript-6


【解决方案1】:

这是一个被称为桶文件的概念。这些文件使导入类更加方便。这不是 Angular 一个人的概念。使用它们似乎是 TypeScript 中的最佳实践。

当您有一个较小的项目时,您可能看不到创建这些文件的太多好处,但它们在处理具有多个团队成员的大型项目时非常有用。以下是我知道的一些优点/缺点。

专业:模块内部所需的知识较少

当一个组件需要引用某个东西时,它不应该知道类等在哪个确切文件中。当你不把东西放在一个模块中时,然后在模块之外的所有其他文件模块需要确切知道包含该项目的文件(包括子路径)。如果您将物品装入单个模块桶中,您只需要知道它来自哪个模块。这也为您提供了重构模块的好处,而无需您确保在文件移动时更新路径(您的工具可能会或可能不会对此有所帮助)。

// without barrel files
import { Class1 } from 'app/shared/module1/subpath1/subpath2/class1.service';

// using barrel files
import { Class1 } from 'app/shared/module1';

专业版:显式导出外部模块

另一个好处是一个模块可能包含一堆类、接口等,这些类、接口等实际上只打算在该模块中使用。通过为模块创建桶文件,您可以让其他开发人员知道在模块之外使用什么。此外,您让他们确切地知道要使用哪些项目,因此他们无需四处寻找他们需要的界面(同样,您的工具可能会或可能不会帮助)。

// export these items from module as others need to have references to these
export { ExampleModule } from './example.module';
export { ExampleService } from './example.service';
export { ExampleInterface } from './example.model';

// these also exist in module but others don't need to know about them
// export { ExampleComponent } from './example.component';
// export { Example2Service } from './example2.service';
// export { ExampleInterface2 } from './example.model';

专业版:更清洁的导入

我要提到的最后一个好处是它有助于清理您的导入。它不仅更清楚哪些项目来自哪些模块,还有助于使导入的from 部分更短,因为您不需要遍历子目录等。请注意,最好实践似乎是将桶文件放在文件夹中的index.ts,因为 TypeScript 编译器会在给定要从中导入的文件夹时默认查找该文件。

// without barrel files (hopefully they would be not randomly ordered to make it even harder...)
import { Class1 } from 'app/shared/module1/subpath1/subpath2/class1.service';
import { Class2 } from 'app/shared/module1/subpath1/class2.component';
import { Interface1 } from 'app/shared/module1/module1.model';
import { Class3} from 'app/shared/module2/subpath3/class3.service';
import { Interface2 } from 'app/shared/module2/module2.model';

// using barrel files
import { Class1, Class2, Interface1 } from 'app/shared/module1';
import { Class3, Interface2 } from 'app/shared/module2';

骗局:附加文件

在尝试识别缺点时,我唯一能想到的是您正在为每个模块创建另一个文件(或者可能是子文件夹,具体取决于您的操作方式)。据我所知,它没有运行时或编译时影响。

【讨论】:

  • 非常感谢!我从来没有深入研究过官方文档来看到这一点,但是每当我们从 angular 导入模块时我都注意到了类似的东西,尽管我很担心,因为我以前从未见过它在使用中。好吧,你说服了我,至少现在我有一些东西支持之前的说法。
猜你喜欢
  • 2020-11-17
  • 2020-01-10
  • 2018-06-10
  • 2018-08-26
  • 2021-03-13
  • 1970-01-01
  • 1970-01-01
  • 2015-10-23
  • 2019-10-27
相关资源
最近更新 更多