借助自定义元素,您可以构建自己的 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;
...
}
}