设计模式#

在整个仓库中重复出现了几种设计模式。本指南旨在补充TypeScript 风格指南

TypeScript#

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

初始化选项#

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

ContentFactory 选项#

小部件的一个常见选项是 IContentFactory,它用于自定义小部件中的子内容。
如果没有给出,如果不需要参数,则使用 defaultRenderer 实例。这样,可以在不继承小部件的情况下对其进行自定义,并且小部件可以支持对其嵌套内容的自定义。

静态命名空间#

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

私有模块命名空间#

“私有”模块命名空间用于对不打算导出且可能以模块级别变量和函数形式存在的变量和函数进行分组。使用命名空间还可以清楚地表明变量访问是针对导入名称还是来自模块本身。最后,如果需要,命名空间可以使整个部分在编辑器中折叠起来。

可处置对象#

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

消息#

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

信号#

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

模型#

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

Getter 与方法#

当返回值必须每次都计算时,优先使用方法。对于简单的属性查找,优先使用 getter。Getter 应该每次都产生相同的值。

数据结构#

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

对于以下情况,优先使用 Array

  • intended 可变的值。

优先使用 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 的浏览器提供一个 shim。在处理已解决或已拒绝的 Promise 时,请务必在继续之前检查当前状态(通常通过检查 .isDisposed 属性)。

命令名称#

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

对话框#

带有一个关闭按钮的对话框(例如,关于 JupyterLab)中的按钮应具有以下属性:

  • 按钮变体:取消 (Dialog.cancelButton())

  • 标签:关闭