自定义元素最佳做法

借助自定义元素,您可以构建自己的 HTML 标记。本核对清单涵盖一些最佳做法,可帮助您制作优质元素。

通过自定义元素,您可以扩展 HTML 并定义自己的代码。它们是 功能极其强大,但它们的级别也很低,也就是说, 始终清楚如何以最佳方式实现您自己的元素。

为帮助您尽可能创造最佳的体验,我们在下面汇总了 核对清单。它分解了我们认为成为在线旅游平台所需要具备的所有条件 行为良好的自定义元素。

核对清单

阴影 DOM

创建影子根来封装样式。

为什么? 在元素的影子根中封装样式可确保它可以正常运行 无论在何处使用它如果开发者需要 希望将您的元素放在另一个元素的影子根中。这个 甚至适用于复选框或单选按钮等简单元素。它的 影子根中的唯一内容就是样式 。
示例 <howto-checkbox> 元素。

在构造函数中创建影子根。

为什么? 当您对元素拥有专有知识时,才属于构造函数。 设置您不想要其他实现细节的理想时机 混乱的元素在稍后的回调中执行此操作,例如 connectedCallback,这意味着您需要防止 元素分离并重新附加到文档的情况。
示例 <howto-checkbox> 元素。

将元素创建的所有子项放入其影子根中。

为什么? 由您的元素创建的子元素是其实现的一部分,应 私密。如果不保护影子根,在 JavaScript 外部 干扰这些孩子。
示例 <howto-tabs> 元素。

使用 <slot>将 light DOM 子项投影到 shadow DOM 中

为什么? 允许组件用户将您的组件内容指定为 HTML 子项,使组件的可组合性更高。如果浏览器不支持自定义元素,嵌套内容将保持可用、可见和访问。
示例 <howto-tabs> 元素。

设置 :host 显示样式(例如 blockinline-blockflex),除非您偏好使用默认值 inline

为什么? 默认情况下,自定义元素为 display: inline,因此设置其 widthheight 将不会产生任何影响。经常这样 并可能会给开发者带来 设置网页布局。除非您更喜欢 inline 显示屏,否则 应始终设置默认的 display 值。
示例 <howto-checkbox> 元素。

添加遵循隐藏属性的 :host 显示样式。

为什么? 采用默认 display 样式的自定义元素,例如 :host { display: block },将替换特异性较低的规则 内置 <ph type="x-smartling-placeholder"></ph> hidden 属性。 如果您希望将 hidden 设置为 属性,以 display: none 呈现该元素。此外 默认为 display 样式,添加对 hidden 的支持 与:host([hidden]) { display: none }共享。
示例 <howto-checkbox> 元素。

特性和属性

请勿覆盖作者设置的全局属性。

为什么? 全局属性是指出现在所有 HTML 元素上的属性。部分 示例包括 tabindexrole。自定义元素 可能希望将其初始 tabindex 设置为 0,使其成为键盘按键 可聚焦但您应该先检查开发者是否使用 您的元素将此设为其他值。例如,如果他们设置了 tabindex 转换为 -1,即表示不希望出现 具有互动性
示例 <howto-checkbox> 元素。有关详情,请参阅 不覆盖网页作者。

始终接受原始数据(字符串、数字、布尔值)作为任一属性 或属性。

为什么? 自定义元素(如其内置元素)应可配置。 配置可通过声明、属性或命令方式传入 通过 JavaScript 属性进行设置理想情况下,每个属性也应关联到 相应的属性。
示例 <howto-checkbox> 元素。

旨在使原始数据属性与属性保持同步, 属性,反之亦然。

为什么? 您永远不知道用户会如何与元素互动。他们可能 在 JavaScript 中设置属性,然后预期会读取该值 使用 getAttribute() 等 API。如果每个属性都有 两者都反映 使用您的元素。也就是说,调用 setAttribute('foo', value) 还应设置相应的 foo 属性,反之亦然。当然, 此规则。您不应反映高频属性,例如 currentTime。请自行判断。如果 用户似乎将与某个属性或属性互动,并且 这样反思并不麻烦
示例 <howto-checkbox> 元素。有关详情,请参阅 避免再次进入问题

力求仅接受丰富的数据(对象、数组)作为属性。

为什么? 一般来说,没有内置 HTML 元素示例 可接受丰富数据(普通 JavaScript 对象和数组), 属性。而是通过方法调用或 属性。将丰富的数据作为 属性:将大型对象序列化为字符串的成本很高; 在此字符串化过程中,所有对象引用都将丢失。对于 例如,如果您将一个引用了另一个对象的对象字符串化, 也可能是 DOM 节点,这些引用都会丢失。

不向属性反映丰富的数据属性。

为什么? 将丰富的数据属性反映到属性不必要地代价高昂, 需要对相同的 JavaScript 对象进行序列化和反序列化。除非 您有一个应用场景只能通过这项功能解决, 请尽量避免

建议检查是否在元素之前设置的属性 已升级。

为什么? 使用该元素的开发者可能会尝试为该元素设置属性 在其定义加载完毕之前如果将 开发者使用的框架来处理组件加载 并将其属性绑定到模型。
示例 <howto-checkbox> 元素。详细说明 使属性延迟

请勿自行应用课程。

为什么? 需要表达状态的元素应使用属性来表达其状态。通过 class 属性通常被视为 开发者使用您的元素,而您自己写入元素可能会无意中 深入探索开发者课程

事件

分派事件来响应内部组件活动。

为什么? 您的组件可能包含一些属性,它们会随着 只有您的组件知道,例如,计时器或动画 或资源完成加载。这有助于分派事件 以便通知主机组件状态 与众不同。

不分派事件来响应主机设置属性(向下 数据流)。

为什么? 为响应主机设置属性而分派事件是多余的 (主机知道当前状态,因为它只是进行了设置)。调度事件 对主机设置属性做出响应,可能会导致数据出现无限循环 绑定系统
示例 <howto-checkbox> 元素。

释疑类视频

不覆盖网页作者

使用该元素的开发者可能会想要覆盖某些 初始状态例如,使用以下代码更改其 ARIA role 或可聚焦性 tabindex。检查是否设置了这些以及任何其他全局属性, 然后再应用您自己的值

connectedCallback() {
  if (!this.hasAttribute('role'))
    this.setAttribute('role', 'checkbox');
  if (!this.hasAttribute('tabindex'))
    this.setAttribute('tabindex', 0);

使属性延迟

开发者可能会尝试在元素的 定义已加载。如果开发者使用 该框架负责处理加载组件、将组件插入页面以及 将其属性绑定到模型。

在以下示例中,Angular 以声明方式绑定其模型的 isChecked 属性设置为复选框的 checked 属性。如果 Howto-checkbox 被延迟加载,Angular 可能会尝试设置 在元素升级前检查选中的属性。

<howto-checkbox [checked]="defaults.isChecked"></howto-checkbox>

自定义元素应通过检查是否有任何属性具有 都已在其实例上进行了设置<howto-checkbox> 使用名为 _upgradeProperty() 的方法演示此模式。

connectedCallback() {
  ...
  this._upgradeProperty('checked');
}

_upgradeProperty(prop) {
  if (this.hasOwnProperty(prop)) {
    let value = this[prop];
    delete this[prop];
    this[prop] = value;
  }
}

_upgradeProperty() 会捕获未升级实例的值并删除 属性,这样它就不会覆盖自定义元素自身的属性 setter。 这样,当该元素的定义最终实际加载时,它可以立即 以反映正确的状态

避免重复问题

很容易使用 attributeChangedCallback() 将状态反映到 例如:

// When the [checked] attribute changes, set the checked property to match.
attributeChangedCallback(name, oldValue, newValue) {
  if (name === 'checked')
    this.checked = newValue;
}

但是,如果属性 setter 也会反映为 属性。

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    // OOPS! This will cause an infinite loop because it triggers the
    // attributeChangedCallback() which then sets this property again.
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

另一种方法是让属性 setter 反映到该属性,并且 让 getter 根据该属性确定其值。

set checked(value) {
  const isChecked = Boolean(value);
  if (isChecked)
    this.setAttribute('checked', '');
  else
    this.removeAttribute('checked');
}

get checked() {
  return this.hasAttribute('checked');
}

在此示例中,添加或移除属性也会设置该属性。

最后,attributeChangedCallback() 可用于处理附带效应 例如应用 ARIA 状态

attributeChangedCallback(name, oldValue, newValue) {
  const hasValue = newValue !== null;
  switch (name) {
    case 'checked':
      // Note the attributeChangedCallback is only handling the *side effects*
      // of setting the attribute.
      this.setAttribute('aria-checked', hasValue);
      break;
    ...
  }
}