Dojo 进阶

官网 https://dojo.io

序言 - 构建企业级 web 应用程序

在热衷敏捷交付的时代,鼓励将小功能点持续地交付给用户。软件行业开始青睐这种方式,因为它最大限度地降低风险,并最大限度地提高用户的参与度和满意度。

即使采用现代的交付方式,一些风险仍然不可避免。复杂性就是这样一种风险,对于成熟的应用程序而言,复杂性更成为一个重要的关注点。无论应用程序遵循什么样的系统架构,随着时间的推移,许多小功能聚集出一个庞大且令人畏惧的代码库,需要几个团队监督。

应用程序上线的时间越久,实现一个设计简洁的新功能的机会就越少。相反,更多的是在现有功能的基础上调整、修复 bug 或扩展。一个成功的应用程序——以及所包含的功能——大部分时间都花在维护上。

维护复杂的应用程序需要经过严格的训练。团队很容易陷入泥潭,将时间花在抱怨代码和同事上面,而不是向用户交付价值。要降低这种风险涉及很多方面,包括标准化、模式化、技术选型和工具等领域。

管理复杂性

在软件交付的生命周期中,错误发现的越早越好。在开发阶段修复一个错误,比在交付环节修复错误,或者已给用户带来负面影响的上线阶段修复错误要快的多,成本也低得多。

强类型

早期捕获错误的好方法是在应用程序的开发阶段支持强类型。如果应用程序的代码中显式指定了类型信息,就可以避免数据类型不匹配而导致的逻辑错误。编译器和静态类型检查程序可以验证类型信息,并当类型不匹配时让构建失败。只有修复了这些错误,软件才能从个人的工作空间进入到交付管道的后续环节。

Dojo 构建在 TypeScript 之上,提供了显式的类型和静态编译时的类型检查。使用 Dojo 构建的应用程序可以用到 TypeScript(而不是普通的 JavaScript)带来的优势。

当使用 Dojo CLI 构建应用程序时,应用程序的构建过程会默认包含 TypeScript 的编译阶段。开发人员从一开始就能编写出类型安全的应用程序。

模块化——单一职责原则

理想情况下,一个组件应该足够小,以便它只实现单一职责。一个组件越简单、封装程度越高,则大量程序员长期理解和维护时就越容易。拥有庞大代码库的大型应用程序就是由大量的更小、更容易理解的组件组合而成的。

试图降低复杂度时,在单个组件中隔离职责有以下好处:

  • 限制范围。假设组件维护一套一致的 API,则能在不影响外部用户使用的情况下更改内部实现。相反,组件的详细信息保存在定义模块的内部,这意味着其定义不会与其他组件的定义冲突,所以就不会与其他组件的命名约定重叠。
  • 简化测试需求,因为单元测试只需关注单一职责,而不是多个条件组合成的越来越多的应用程序逻辑。
  • 组件可以在多处重用,避免多处重复实现。修复错误时,只需专注于单个组件,而不是散落在各处的单独实例。

对于 web 应用程序而言,隔离还能为终端用户带来额外的好处。应用程序可以划分为多个层,用户在给定的时间点只加载他们感兴趣的层。这减少了资源大小和网络传输需求,从而缩短了加载时间。

Dojo 应用程序的组件

Index 网页

HTML 页面是每个应用程序的基本内容,Dojo 应用程序也不例外。传统上,单个 index.html 文件既是应用程序的入口点,也是将应用程序的整体结构存入 DOM 的根容器。

Dojo 应用程序通常会注入到单个 DOM 元素中,默认情况下是注入到 document.body 中。这使得 Dojo 应用程序可以与页面中的其他内容共存——静态资源、传统的应用程序、甚至是另外一个 Dojo 应用程序。

部件

Dojo 中的部件与 DOM 元素类似,是 Dojo 应用程序中封装的核心概念。正如传统网站是通过 DOM 元素逐层构建的一样,Dojo 应用程序则是通过部件逐层构建的。

部件可以描述一切,从单个界面元素(如 label 或 textbox)到更复杂的容器(如 form 表单、页面或者整个应用程序)。

类似地,并非 DOM 中的所有元素都对用户可见,Dojo 部件不仅提供用户界面,也可以实现应用程序的所有幕后需求。

详见创建 Dojo 部件参考文档,了解如何在应用程序中创建部件。

TypeScript 模块

Dojo 部件可以是一个渲染函数工厂或者 TypeScript 类,通常包含在单个 TypeScript 模块中。该模块封装了组成部件的大部分内容,包括它的行为以及虚拟 DOM 的语义化表示。

部件通过属性接口向外部消费者提供 API。这个接口既可包含状态字段列表,在渲染时注入到部件中;也可以包含函数,当事件发生时,部件需要通知应用程序的其他部分时调用,比如部件状态的变更。

CSS 模块

部件的外观样式是交由 CSS 设置的,与常规的 HMTL 元素样式类似。CSS 模块用于封装单个部件的样式,避免与其他部件的 CSS 类名冲突。

部件导入 CSS 模块跟导入其他 TypeScript 模块一样,并允许通过对象属性引用 CSS 类名,这些属性会在开发人员的 IDE 中自动提示。在定义部件的语义元素结构时,可以使用这些属性名指定样式类。部件中的 CSS 类名和最终的样式类名不一致,而这可以在构建阶段识别出来。

虽然部件的 CSS 模块可以完全封装自身的样式,但通常也需要一些灵活性。部件可以在应用程序的不同配置下使用,每个配置都有自己独特的外观需求。Dojo 提供了覆盖特定样式的能力以满足这个需求。

为了支持应用程序层面外观的一致性,可以通过主题进一步控制部件的样式。

详见 Dojo 样式和主题参考文档,了解如何为单个部件设置样式。

状态管理

企业应用程序通常需要持久化状态,并允许用户以各种方式查看和操作这些数据。当需要同时在多处访问和编辑同一数据,且要保持数据的一致性时,状态管理可以成为大型应用程序中最复杂的领域之一。

状态通常存在位于 web 应用程序组件外部的数据存储或数据库中,这意味着一些状态管理复杂性需要在应用程序之外解决。然而,对于数据在应用程序与其用户之间流动的情况,有几个范例能够大大的降低管理复杂状态的风险。

响应式的数据修改

以命令式方式编写的应用程序会描述应更改哪些数据,应该如何更改,以及指定必须在何时何地更改。如果通过某种形式的计算或赋值在逻辑上连接多块数据,则这些连接在一段时间后只能表示成离散的点。久而久之,除了这些点之外,可能会以违反预期逻辑连接的方式修改任何数据值。

相反,以响应式方式编写的应用程序设法提升数据之间的逻辑连接,并放弃对明确地指定何时何地修改数据的控制,以使逻辑数据连接始终保持一致。

具有多个服务层的复杂应用程序可能会在多处描述相同的数据,因为它们散落在应用程序的各处——这方面的一种常见模式是使用数据传输对象。一段数据的描述位置越多,则维护应用程序状态完整性的复杂度呈指数级增长。

任何应用程序只要 UI 需要动态展示(包括 web 应用程序),都会遇到维护逻辑数据连接一致性的问题。这些应用程序中的数据通常至少有两种表示方式。

举例说明问题

假设有一个代办事项应用程序,它存储了一组任务,当向用户显示时,每个任务都有以下两种数据表示方式:

  • 任务的确切描述(它的“真实来源”,例如它在数据存储中的值)
  • 任务描述的副本,通过 UI 元素(如 label 或 textbox)呈现给用户。

如果用户只能查看任务,则有几个问题与如何修改任务的描述以让用户可见有关。

如果在底层的数据存储中更改了任务,则需要通过 UI 向上传播新的描述信息,这样用户就不会查看过时的数据。如果任务显示在 UI 的多个位置,则所有实例都需要更新,确保用户不会在不同位置看到的数据不一致。

如果用户还可以修改任务(比如更改描述信息),则还需要解决其他问题。

任务描述现在有两处真正的来源:数据存储中的旧值,以及用户在 textbox 中输入的新值。

然后,需要将修改请求传回给底层的数据存储,以便用新值替换旧值。修改完成后,需要将新的任务描述返回给用户,让用户看到更改后的正确值。尝试修改任务描述时会发生的任何错误也需要在数据交换时考虑。

Dojo 的状态管理

对于最基本的状态管理需求,部件可以使用本地变量管理自身的状态。虽然这种方法有助于隔离和封装,但它只适用于非常简单的用例,如只在应用程序中出现一次的部件,或者与应用程序处理的所有其他状态都断开了连接的部件。

随着在部件间共享状态的需求增加,Dojo 支持响应式的控制反转。将状态提升到父容器部件中,然后使用子部件的 properties 接口注入到子部件中。如果需要,这种状态提升可以横穿整个部件层级,将状态集中在应用程序根部件中,然后将部分状态注入到相关的子分支中。

对于更复杂的需求,或者对于较深的部件层级且不希望在不相关的中间层传递状态,则外部的数据存储可能是最好的方法。集中的数据存储能够帮助应用程序处理大量的状态,允许复杂的状态编辑操作,或者在多处请求相同的状态子集。

Dojo 提供了一个 Store 组件,它支持多种高级的状态管理需求,例如:

  • 内置支持异步调用,例如调用远程服务进行数据管理。
  • 状态操作按确定的顺序执行。
  • 记录状态操作历史,允许回滚或撤销操作。
  • 中间件用于包装数据操作流程,可添加横切点,如用于授权或记日志。
  • 内置支持基于 LocalStorage 的数据存储,有助于实现 PWA。
  • 支持乐观的数据更新,失败时会自动回滚。

用户体验

Web 应用程序本质上是通过用户界面提供体验的,应用程序的作者需要考虑各种因素,以向用户展示最好的界面。一致的可视化外观和可访问性通常是最显眼的因素,但也需要关注效率和性能,无论是应用程序的逻辑,还是交付的内容,都有助于提升 web 应用程序的用户体验。

主题

应用程序提供最佳用户体验的一种方式是向最终用户提供一致的外观。这可能与在类似的元素中使用一致的字体一样简单,但通常会扩展到使用相同的色调显示应用程序,甚至实现一整套设计语言,如 Material Design。

Dojo 的样式管道使用 CSS 模块将样式规则封装到特定的部件中,避免在大型代码库中交叉污染。但是,样式并不是完全隔离的——集中的 CSS 变量能够定义公共的主题属性并在应用程序的所有部件间共享。也可以为 Dojo 部件套件提供自定义主题。

详见 Dojo 样式和主题参考文档,了解如何创建应用程序主题。

UI 部件套件

通过部件套件,Dojo 提供了一些现成的 UI 组件。开发人员可以立即使用这些部件制作许多常见的页面,如 combobox、button、list、tab、text input 和 calendar 等部件。

Dojo 的部件支持国际化、可访问性主题,让开发人员在无需自定义 UI 组件的情况下,能够灵活的交付应用程序专有的用户体验。

导航路由

虽然有些应用程序为用户提供了一个主视图,其中可以处理大部分工作,但很多应用程序中用户需要访问更多的区块。帮助页面、设置面板或者分步骤工作流这些例子中,应用程序可能有多个界面,用户可以在任何时间访问这些界面。

应用程序的每块内容都需要唯一标识符,这样用户就可以访问它们。这些标识符也必须要支持为链接设置书签和分享链接,以便跳转到应用程序特定区块。用户也需要在不同区块间导航,以便可以访问应用程序提供的所有功能。导航可以前进到下一步、后退到上一步或者根据用户的选择在多个选项间跳转。

使用静态文件的传统网站包含可单独识别的内容,因为站点中的每个静态文件都能单独访问。HTML 文件可使用锚点元素,通过点击链接在不同文件间导航,而不必手动修改浏览器地址栏中的 URI。

顾名思义,单页面 web 应用程序只有一个主文件,用户通过该文件访问整个应用程序。但是,这些单页面应用可以使用 URI(连同 URI 已有的优点)来标识每一个小节。

路由组件为跨层级的路由提供了导航选项,并会将相应的已标识的路由分发到相应的应用程序区块。路由还将处理任何错误条件,例如导航到不存在的路由。

Dojo 路由

Dojo 的路由系统允许将 URL 的子路径注册为路由,以链接到某个特定类型部件上,这个特定类型的部件称为 Outlet。当用户导航到特定的路由时,将会渲染注册到该路由上的 Outlet 部件。

当用户导航到 Outlet 时,就会“渲染” Outlet,但 Outlet 很少直接处理应用程序的渲染。Outlet 主要是处理导航的封装器(传入查询参数或者处理错误的回调),而将渲染功能委托给应用程序中的其他部件。

类似于在传统 HTML 页面中使用的锚点,应用程序可以使用与 Outlet 关联的 Link 部件向用户提供导航选项。

当使用路由时,Dojo 的构建系统能为应用程序中的所有顶级路由自动生成单独的包。然后可以根据需要将每个包独立的交付给用户。

详见 Dojo 路由参考指南,了解如何在自己的应用程序中实现路由。

效率和性能

高效的渲染

动态网站内容(即包含 JavaScript)成为 web 的一部分已经有很多年了。长期以来,站点就可以包含一些脚本来操作 DOM,进行添加、更新或删除内容。但是,Web 的起源(至今仍然是它的一大关键特征)是以静态页面为基础的。随着时间的推移,浏览器的 DOM 实现得到了优化,以便尽可能高效地、快速地向最终用户渲染静态内容。

近年来,随着 web 应用程序越来越复杂,浏览器已通过 DOM 性能优化做出了回应,针对动态内容作了优化。然而,为了渲染用户界面,web 应用程序仍然需要与一套几十年不变的命令式 API 交互。围绕响应式数据传播而设计的现代 web 应用程序需要一种更高效的方式,将用户界面转换为网页的 DOM。

Dojo 将 DOM 从应用程序中抽象出来,推荐使用响应式状态流来最小化应用程序的样板文件,同时提高了渲染性能。部件会在渲染函数中输出虚拟节点,这些渲染函数使用虚拟 DOM 描述部件的结构层级。然后,框架以尽可能高效的方式处理 VDOM 的渲染,只会影响实际需要修改的 DOM 元素。

需要从 DOM 中获取具体信息来实现其需求的应用程序,Dojo 通过中间件系统提供了另一种 DOM 抽象层。Dojo 中间件以一致的方式解决了这些问题,并仍然支持横跨应用程序的响应式数据流。

应用程序的交付——分层和包

随着 web 应用程序规模的增长,当一个任务只需要访问一部分资源时,却必须加载应用程序的所有资源,这样效率就会越来越低。每一个应用程序资源都有一个与大小相关的成本:内存空间需求和网络上的数据传输;所有这些都会影响到用户开始工作之前需要的等待时间。让应用程序只在需要的时候加载所需的内容,从而将此成本保持在最低水平,这符合用户的最大利益。

获取应用程序的资源时,在 HTTP 资源协商方面会产生额外的开销。客户端需要请求数据,然后客户端必须等待服务器发送完资源的最后一个字节。更严重的情况下,开销还包括 DNS 解析、TCP 连接重建和 TLS 密码/证书协商。

浏览器可以有效地减少这一开销,但是浏览器不能完全消除这一开销——应用程序也有责任来减少资源传输的开销。与资源的大小相比,获取一个应用程序资源的开销是相对不变的。获取1KB 文件的开销与获取100KB 文件的开销类似。

因此可以通过两种方式降低开销:减少资源总数和增加单个资源的大小。web 应用程序可以通过分层和将相关的资源打包来实现这两种方式。

单个层中应该包含应用程序中特定功能相关的资源集。当用户访问该功能时,层中的所有资源可能同时加载。然后一个层包含的所有内容都可以打包到一个文件中,以便更高效地传送给用户。

自动分层

当使用 Dojo 的路由系统时,应用程序可以从自动分层和打包中获益。应用程序中的每个顶级路由都成为一个单独的层,Dojo 的构建系统会自动打包每层内容。这样就可以对层分离,以及打包资源,而不需要配置额外的工具链。这种自动化方案有一处折衷,即在每个包中都内联和复制了跨多个层的公共依赖项。

声明分层

复杂的应用程序可能需要对层或包的定义做更细粒度的控制。例如,如果应用程序有一组横跨多个路由的公共依赖项,不要在每个包中内联或复制这些依赖,则需要将公共依赖提取到自己的包中,然后在第一次引用时延迟加载。

Dojo 的构建管道允许在应用程序的 .dojorc 构建配置文件中指定资源,然后能自动将横跨多个包的模块依赖项转换为延迟加载的引用。

可访问性与国际化

Web 本质上是全球性的,为其编写的应用程序需要支持所有用户。文本需要按用户选择的语言和脚本显示,并且需要根据用户的区域设置对日期、时间、数字和货币等值进行相应的格式化。

Dojo 允许轻松使用消息包将文本消息从应用程序逻辑中分离出来,然后根据需要选择使用 Unicode CLDR 数据的相关部分支持更高级的值格式化。

开发 web 时,需要应用程序对用户足够包容,不论用户是否需要可访问性。W3C 的可访问性提案已经帮助标准化了许多这方面的需求,包括对可访问的富 Internet 应用程序做的额外工作。

使用 Dojo 的部件套件开发的应用程序已提供了现成的 WAI-ARIA 属性。虽然 Dojo 在这一点上提供了帮助,但它只也只能做这么多——应用程序作者有额外的责任来验证他们的应用程序提供的可访问性级别。建议在应用程序的交付生命周期中包含显式的可访问性测试步骤。

详见 Dojo 国际化参考文档,了解如何为全球用户开发 Dojo 应用程序。

可适配的外观

当前社会,Internet 的重要性与日俱增,应用程序被要求能适应用户访问 web 的各种方式。较小尺寸的移动体验已经超过了桌面,但较大的外观仍然可以满足复杂的应用程序需求。Dojo 提供了多种解决方案,帮助开发人员创建适应用户访问需求的应用程序。

当需要预渲染内容时(如开发静态站点时),Dojo 应用程序可以利用构建时渲染(BTR),应用程序结构的一部分或全部都是在构建时计算的,而不是在用户浏览器中运行时计算的。Dojo 提供了一个灵活的基于块 BTR 的解决方案,当构建应用程序时能运行 Node.js 脚本,支持读取文件来获取内容等功能。Dojo 的 BTR 解决方案也支持渐进式融合,以在预渲染内容之上支持动态行为。

渐进式 web 应用程序(PWA)有助于提供与本地设备 App 接近的体验,同时依然能从 web 支持的可移植性和易交付等功能中受益。Dojo 通过简单的构建配置就能帮助创建 PWA,开发人员可以在应用程序中添加离线使用、后台数据同步和推送通知等。

Dojo 允许开发人员通过中间件系统,在所有的交付目标上以一致的方式使用几个即将可用的 web API。Intersection observer API 用于更好的控制渲染,仅渲染用户可见的部件,例如支持无线滚动列表。Resize observer API 能够让应用程序动态响应视窗大小的变化,允许界面在桌面和移动视窗的所有分辨率间逐步适应。

应用程序的开发生命周期

Dojo 为开发 web 应用程序提供了一个端到端的管道。应用程序的作者可以使用 dojo create app CLI 命令快速创建 Dojo 应用程序。然后可以使用 dojo build app 命令在开发模式和生产模式下构建应用程序。使用本地 HTTP 服务运行应用程序,并监视对项目文件的修改,构建工具为快速开发和迭代提供支持。使用这种机制,开发人员可以在运行的应用程序中更改代码并能立即看到结果。

这些命令是模块化 Dojo CLI 工具链的一部分,该工具链支持开发生命周期中的各种使用。通过应用程序根目录下的 .dojorc 配置文件,可以配置应用程序的构建管道。

详见 Dojo 构建参考文档,了解如何使用 Dojo 构建各种应用程序。

测试的策略

编译器和静态类型检查程序无法捕获出所有的错误。编写的功能在语法和逻辑上是有效的,但要么在运行时出现无法预见的问题,要么不是按预期的要求执行功能。为了降低这种风险,需要进行额外的测试。

当使用 Dojo CLI 构建应用程序时,默认会内置一个 Intern 测试库的测试运行器。这样开发人员人员在编写应用程序功能的同时就可以立即编写测试代码。

Intern 为很多测试场景提供了解决方案,但可能不足以满足项目的所有测试需求。Dojo 也提供了一个简单的测试工具,允许应用程序测试代码在 VDOM 的抽象层级验证框架和部件。这个工具可以用在很多测试运行器中,如 Intern、Jest 或应用程序测试策略所需的任何其他程序。

详见 Dojo 测试参考文档,了解如何高效测试 Dojo 应用程序。

你可能感兴趣的:(dojo,typescript)