Dojo 中的样式和主题

Dojo 部件最适合作为简单的组件,每个组件处理单一职责。它们应该尽可能的封装和模块化,以提高可重用性,同时避免与应用程序使用的其他组件出现冲突。

可以使用常规的 CSS 为部件设置样式,但是为了达到封装和复用的目标,每个部件应该维护各自的 CSS 模块(CSS module),该模块与部件的源代码存放在各自的文件中。这样就可以独立地设置各部件的样式,而不会与应用程序其他地方使用的类名冲突。

Dojo 界定出以下几类样式,每一类都代表了企业 web 应用程序中的样式需关注的不同方面和粒度:

如上述列表所示,无论是跨整个应用程序,还是单个样式类中的单条样式规则,Dojo 为应用程序开发者提供了几种互相补充的机制来提供或重写 CSS 样式类。

部件的结构样式

Dojo 借助 CSS Modules,既提供了 CSS 的所有灵活性,又引入了本地化样式类的额外优势,防止大型应用程序中无意间的样式冲突。Dojo 也为每个 CSS 模块生成类型定义文件,允许部件用与导入其他 TypeScript 模块相似的方式来导入 CSS 模块,并以类型安全的方式引用 CSS 类名,同时在设计期间可以使用 IDE 自动完成功能。

部件的 CSS 模块文件应该使用 .m.css 扩展名,并约定 CSS 模块的文件名要与关联的部件名保持一致。具有此扩展名的文件会被当作 CSS 模块来处理,而不是普通的 CSS 文件。

示例

以下是部件的 CSS 模块文件:

src/styles/MyWidget.m.css

  1. .myWidgetClass {
  2. font-variant: small-caps;
  3. }
  4. .myWidgetExtraClass {
  5. font-style: italic;
  6. }

在对应的部件中使用此样式,如下所示:

src/widgets/MyWidget.ts

  1. import { create, tsx } from '@dojo/framework/core/vdom';
  2. import * as css from '../styles/MyWidget.m.css';
  3. const factory = create();
  4. export default factory(function MyWidget() {
  5. return <div classes={[css.myWidgetClass, css.myWidgetExtraClass]}>Hello from a Dojo widget!</div>;
  6. });

在构建的应用程序中查看示例部件中的 CSS 类时,它们不会直接包含 myWidgetClassmyWidgetExtraClass,而是经过混淆处理的 CSS 类名,类似于 MyWidget-m__myWidgetClass__33zN8MyWidget-m__myWidgetExtraClass___g3St

混淆后的类名专用于 MyWidget 元素,而这是由 Dojo 的 CSS 模块化构建流程决定的。有了这种机制,则同一个应用程序的其他部件也可以使用 myWidgetClass 类名,即使具有不同的样式规则,也不会在每组样式间出现任何冲突。

警告: 混淆处理的 CSS 类名是不稳定的,可能会随着应用程序的构建而更改,所以开发人员不能显式地引用它们(例如试图在应用程序的其他位置定位一个元素)。

提取和扩展样式

CSS 自定义属性

Dojo 可以使用现代的 CSS 特性,例如自定义属性和 var(),来提取和集中管理应用程序中的通用样式属性。

不必在每个部件的 CSS 模块中为颜色或字体设置相同的值,而是通过提取自定义属性,在每个 CSS 模块中引用该属性名,然后在集中一处的 .root 样式类设置主题的 variant。这种隔离更易于维护跨整个应用程序的公共样式,并可以通过更改变量创建主题的变体。

注意:不要在部件的 CSS 模块中导入主题变体文件;这是在运行时通过 theme.variant() 类处理的。

例如:

src/themes/MyTheme/variants/default.m.css

  1. .root {
  2. --dark-background: black;
  3. --dark-foreground: lightgray;
  4. --padding: 32px;
  5. }

src/themes/MyTheme/MyWidget.m.css

  1. .root {
  2. margin: var(--padding);
  3. color: var(--dark-foreground);
  4. background: var(--dark-background);
  5. }

Dojo 默认的构建流程按原样将自定义属性输出到应用程序的样式表中。对于最新的浏览器来说,这样做没有问题;但当使用的浏览器没有实现 CSS 自定义属性标准(如 IE)时,就会出现问题。为了解决这个问题,可以使用遗留模式(dojo build app --legacy)来构建应用程序,这种情况下,Dojo 会在构建期间解析自定义属性的值,并复制到输出的样式表中。一个值将包含原来的 var() 引用,第二个值是专为旧版浏览器解析的值,当无法处理 var() 时就使用解析后的值。

CSS 模块的组合功能

将主题应用到 Dojo 部件后,部件的默认样式类会完全被主题提供的样式类覆盖。当只需要通过主题修改样式类中的一部分属性,而其余属性依然使用默认值时,就会出现问题。

Dojo 应用程序中的 CSS 模块文件可以使用 composes: 功能将样式从一个类选择器应用到另一个类选择器。当通过调整现有的主题来创建一个新主题时,或者在单个主题中提取通用的样式属性时(注意,提取单个属性的值是更标准的做法是 CSS 自定义属性),这个功能是很有用的。

警告: composes: 功能可能会比较脆弱,例如当扩展一个不受当前应用程序控制的第三方主题时。第三方主题所做的任何更改,都可能会破坏基于 composes 功能的应用程序主题,且这样的破坏很难定位和解决。

但是,在大型应用程序中仔细使用此功能会很有用。比如,集中管理一组公共属性:

src/themes/common/ButtonBase.m.css

  1. .buttonBase {
  2. margin-right: 10px;
  3. display: inline-block;
  4. font-size: 14px;
  5. text-align: left;
  6. background-color: white;
  7. }

src/themes/myBlueTheme/MyButton.m.css

  1. .root {
  2. composes: buttonBase from '../common/ButtonBase.m.css';
  3. background-color: blue;
  4. }

Dojo 样式最佳实践

由于 Dojo 应用程序中的样式主要是针对单个部件的,因此不需要使用复杂的选择器。在 Dojo 中为应用程序设置样式时应尽可能简单,开发人员可通过以下几条简单的建议做到这一点:

  • 维护封装的部件样式
    • 一个 CSS 模块应只解决一个问题。与部件一一对应的 CSS 模块,通常只包含对应单个部件的样式类。CSS 模块也可以被多个部件共享,比如应用程序定义一个跨整个应用程序的、公共的排版模块。在 TypeScript 代码中,部件引用多个 CSS 模块也是一种常见的做法。
    • 不要在 CSS 模块之外使用样式类来引用部件,或者使用为部件提供覆盖样式的主题来引用部件。
    • 不要依赖构建的应用程序中的样式类名,因为 Dojo 会对类名做混淆处理。
  • Prefer class-level selector specificity
  • 优先使用类选择器(class selector)
    • 不应使用类型选择器(type selector),因为这样做会破坏部件的封装性,可能对使用相同元素类型的其他部件产生负面影响。
    • 不应使用 id 选择器(id selector)。Dojo 部件旨在封装和复用,而使用元素的 id 违背此目标。Dojo 提供了其他机制来增强或覆盖特定部件实例的样式,如部件的 classestheme 属性。
  • 避免嵌套选择器
    • 部件应该足够简单,只使用一个类选择器。如果需要,部件也能使用多个、独立的类来应用额外的样式。单个部件也可以使用定义在多个 CSS 模块中的样式。
    • 复杂的部件应该被重构为一个简单的父部件和多个简单的子部件,这样可以为单独每个部件封装专用的样式。
  • 避免使用 BEM 命名规范
    • 优先使用与部件的用途相关的类名
  • 避免使用 !important