设计模式#

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

TypeScript#

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

初始化选项#

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

内容工厂选项#

A common option for a widget is a IContentFactory, which is used to customize the child content in the widget.
If not given, a defaultRenderer instance is used if no arguments are required. In this way, widgets can be customized without subclassing them, and widgets can support customization of their nested content.

静态命名空间#

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

私有模块命名空间#

“Private”模块命名空间用于分组那些不打算导出且可能作为模块级变量和函数存在的变量和函数。使用命名空间还可以清楚地表明变量访问是来自导入的名称还是模块本身。最后,如果需要,命名空间允许在编辑器中折叠整个部分。

一次性用品#

JavaScript does not support “destructors”, so the IDisposable pattern is used to ensure resources are freed and can be claimed by the Garbage Collector when no longer needed. It should always be safe to dispose() of an object more than once. Typically the object that creates another object is responsible for calling the dispose method of that object unless explicitly stated otherwise.
To mirror the pattern of construction, super.dispose() should be called last in the dispose() method if there is a parent class. Make sure any signal connections are cleared in either the local or parent dispose() method. Use a sentinel value to guard against reentry, typically by checking if an internal value is null, and then immediately setting the value to null. A subclass should never override the isDisposed getter, because it short-circuits the parent class getter. The object should not be considered disposed until the base class dispose() method is called.

消息#

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

信号#

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

模型#

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

Getters 对比 方法#

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

数据结构#

对于公共API,我们有三种选择:JavaScript ArrayIIterator,和 ReadonlyArray(由TypeScript定义的接口)。

优先选择Array用于:

  • 一个旨在可变的值。

更喜欢使用 ReadonlyArray

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

  • 一个信号负载 - 因为它将被多个监听器消费。

  • 可能需要随机访问这些值。

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

更倾向于使用 IIterator 用于:

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

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

DOM 事件#

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

承诺#

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

命令名称#

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

对话框#

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

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

  • 标签: Close