借助自定义元素,您可以构建自己的 HTML 标记。本核对清单涵盖一些最佳做法,可帮助您制作优质元素。
通过自定义元素,您可以扩展 HTML 并定义自己的代码。它们是
功能极其强大,但它们的级别也很低,也就是说,
始终清楚如何以最佳方式实现您自己的元素。
为帮助您尽可能创造最佳的体验,我们在下面汇总了
核对清单。它分解了我们认为成为在线旅游平台所需要具备的所有条件
行为良好的自定义元素。
核对清单
阴影 DOM
  
    
      | 创建影子根来封装样式。 | 
    
      | 为什么? | 在元素的影子根中封装样式可确保它可以正常运行
  无论在何处使用它如果开发者需要
  希望将您的元素放在另一个元素的影子根中。这个
  甚至适用于复选框或单选按钮等简单元素。它的
  影子根中的唯一内容就是样式
  。 | 
    
      | 示例 | <howto-checkbox>元素。 | 
  
  
    
      | 在构造函数中创建影子根。 | 
    
      | 为什么? | 当您对元素拥有专有知识时,才属于构造函数。
  设置您不想要其他实现细节的理想时机
  混乱的元素在稍后的回调中执行此操作,例如 connectedCallback,这意味着您需要防止
  元素分离并重新附加到文档的情况。 | 
    
      | 示例 | <howto-checkbox>元素。 | 
  
  
    
      | 将元素创建的所有子项放入其影子根中。 | 
    
      | 为什么? | 由您的元素创建的子元素是其实现的一部分,应
  私密。如果不保护影子根,在 JavaScript 外部
  干扰这些孩子。 | 
    
      | 示例 | <howto-tabs>元素。 | 
  
  
    
      | 使用 <slot>将 light DOM 子项投影到 shadow DOM 中 | 
    
      | 为什么? | 允许组件用户将您的组件内容指定为 HTML 子项,使组件的可组合性更高。如果浏览器不支持自定义元素,嵌套内容将保持可用、可见和访问。 | 
    
      | 示例 | <howto-tabs>元素。 | 
  
  
    
      | 
  设置 :host显示样式(例如block、inline-block、flex),除非您偏好使用默认值inline。 | 
    
      | 为什么? | 默认情况下,自定义元素为 display: inline,因此设置其width或height将不会产生任何影响。经常这样
  并可能会给开发者带来
  设置网页布局。除非您更喜欢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 元素上的属性。部分
  示例包括 tabindex和role。自定义元素
  可能希望将其初始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;
    ...
  }
}