设计模式#

整个代码库中重复使用了几种设计模式。本指南旨在补充 TypeScript 风格指南.

TypeScript#

所有源代码都使用 TypeScript。使用 TypeScript 是因为它提供了最新 EMCAScript 6 标准的功能,同时提供类型安全性。TypeScript 编译器消除了整类错误,同时使代码重构变得更加容易。

初始化选项#

对象通常会有一个 IOptions 接口用于初始化小部件。使用此接口可以稍后添加选项,同时保持向后兼容性。

ContentFactory 选项#

小部件的常见选项是 IContentFactory,它用于自定义小部件中的子内容。
如果没有给出,如果不需要参数,则使用 defaultRenderer 实例。通过这种方式,可以自定义小部件而无需对其进行子类化,并且小部件可以支持对其嵌套内容的自定义。

静态命名空间#

对象类通常会有一个导出的静态命名空间,与对象具有相同的名称。命名空间用于整理类定义。

私有模块命名空间#

“私有”模块命名空间用于对不打算导出的变量和函数进行分组,这些变量和函数可能原本作为模块级变量和函数存在。使用命名空间还可以清楚地表明变量访问是针对导入的名称还是针对模块本身。最后,命名空间允许在需要时折叠编辑器中的整个部分。

可处置对象#

JavaScript 不支持“析构函数”,因此使用 IDisposable 模式来确保在不再需要时释放资源并可以由垃圾收集器回收。多次 dispose() 对象通常是安全的。通常,创建另一个对象的物体负责调用该对象的 dispose 方法,除非另有明确说明。
为了镜像构造模式,如果存在父类,则在 dispose() 方法中最后调用 super.dispose()。确保在本地或父 dispose() 方法中清除所有信号连接。使用哨兵值来防止重新进入,通常通过检查内部值是否为 null,然后立即将该值设置为 null。子类永远不应该覆盖 isDisposed getter,因为它会短路父类 getter。在调用基类 dispose() 方法之前,不应该认为该对象已处置。

消息#

消息旨在用于多对一通信,其中外部对象影响另一个对象。消息可以合并并作为单个消息处理。它们可以在下一动画帧上发布和处理。

信号#

信号旨在用于一对多通信,其中外部对象对另一个对象的更改做出反应。信号始终以发送方作为第一个参数发出,并包含一个带有有效负载的单个第二个参数。信号通常不应用于触发操作的“默认”行为,而是使其他人能够触发其他行为。如果“默认”行为打算由另一个对象提供,则该对象应提供回调。在可能的情况下,信号连接应使用模式 .connect(this._onFoo, this) 进行。提供 this 上下文使连接能够通过 Signal.clearData(this) 正确清除。使用私有方法可以避免为每个连接分配闭包。

模型#

一些更高级的小部件与其关联的模型相关联。常用的模式是模型是可设置的,并且必须在构造函数之外设置。这意味着任何使用小部件的消费者都必须考虑可能为 null 的模型,并且可能随时更改。小部件应发出 modelChanged 信号,使消费者能够处理模型的更改。启用模型交换的原因是,同一个小部件可以用来显示不同的模型内容,同时保留小部件在应用程序中的位置。模型不能在构造函数中提供的原因是,模型所需的初始化可能必须调用子类化的方法。子类化的方法将在子类构造函数完成评估之前被调用,从而导致状态未定义。

Getter 与方法#

如果返回值必须每次都计算,则首选方法。对于简单的属性查找,首选 getter。getter 应该每次都产生相同的值。

数据结构#

对于公共 API,我们有三个选项:JavaScript ArrayIIteratorReadonlyArray(由 TypeScript 定义的接口)。

首选 Array 用于

  • 一个旨在可变的值。

首选 ReadonlyArray 用于

  • 返回值是新分配数组的结果,以避免额外分配迭代器。

  • 信号有效负载 - 因为它将被多个侦听器使用。

  • 可能需要随机访问值。

  • 本质上是静态的公共属性。

首选 IIterator 用于

  • 返回值,其中值基于内部数据结构,但值不需要随机访问。

  • 可以延迟计算的一组返回值。

DOM 事件#

如果一个对象实例需要响应 DOM 事件,则为该类创建一个 handleEvent 方法,并将对象实例注册为事件处理程序。 handleEvent 方法应该根据事件类型进行切换,并可以调用私有方法来执行操作。 通常,小部件类会在其 onAfterAttach 方法中将自身添加为其自身节点的事件监听器,例如 this.node.addEventListener('mousedown', this),并在 onBeforeDetach 方法中使用 this.node.removeEventListener('mousedown', this) 将自身注销。 从 handleEvent 方法分派事件可以更轻松地跟踪、记录和调试事件处理。 有关 handleEvent 方法的更多信息,请参阅 EventListener API。

Promise#

我们使用 Promise 进行异步函数调用,并为不支持 Promise 的浏览器提供垫片。 处理已解析或已拒绝的 Promise 时,请确保在继续之前检查当前状态(通常通过检查 .isDisposed 属性)。

命令名称#

应用程序命令注册表中使用的命令应采用以下格式: package-name:verb-noun。 它们通常在扩展中分组到一个未导出的 CommandIDs 命名空间中。