面向未来的全新组件交互设计 - hi-element

338 阅读3分钟

简介

一个简单的基类,用于创建快速,轻便的Web组件

hi-element库是一种轻量级的方法,可以轻松构建高性能、内存高效、符合标准的Web组件。hi-element适用于所有主流浏览器,可以与任何前端框架结合使用,甚至可以不使用框架。

安装

From NPM

要安装hi-element库,请使用npmyarn,如下所示:

npm install --save hi-element
yarn add hi-element

在JavaScript或TypeScript代码中,您可以像这样导入库API:

import { HIElement } from 'hi-element';

手册指南

模板

虽然可以手动在Shadow DOM中创建和更新节点,但是比较繁琐,HIElement为最常见的渲染场景提供了一个简化的模板系统。要为元素创建HTML模板,请导入并使用“HTML”标记的模板解析器,并将模板传递给@customElement装饰器。

下面是我们如何为name-tag组件添加一个模板,该组件呈现一些基本结构以及greeting

实例: 将模板添加到 HIElement

import { HIElement, customElement, attr, html } from 'hi-element';

const template = html<NameTag>`

  <div class="header">

    <h3>${x => x.greeting.toUpperCase()}</h3>

    <h4>my name is</h4>

  </div>



  <div class="body">TODO: Name Here</div>



  <div class="footer"></div>

`;



@customElement({

  name: 'name-tag',

  template

})

export class NameTag extends HIElement {

  @attr greeting: string = 'Hello';

}

在上面的例子中有几个重要的细节,所以让我们逐一分解。

首先,我们使用标记的模板文字标记为后面的html字符串提供特殊处理,创建一个模板,返回一个ViewTemplate实例。

在模板中,我们提供绑定来声明模板的动态部分。这些绑定是用箭头函数声明的。因为模板是插入的,所以箭头函数的输入将是您在html标记中声明的数据模型的实例。当html标记处理模板时,它会识别这些动态表达式,并建立一个优化的模型,能够进行高性能渲染和高效、增量的批量更新。

最后,我们使用一种新形式的@customElement装饰器将模板与自定义元素关联起来,它允许我们传递更多选项。在这个配置中,我们传递一个options对象,指定nametemplate

有了这个元素,我们现在有了一个name-tag元素,它将把它的模板呈现到Shadow DOM中,并在name tag的greeting属性发生变化时自动更新h3内容。试试看!

类型化模板

模板可以插入到它们正在渲染的数据模型中。在TypeScript中,我们将类型作为标记的一部分提供:html<NameTag>。对于TypeScript 3.8或更高版本,可以将ViewTemplate作为类型导入:

import type { ViewTemplate } from 'hi-element';

const template: ViewTemplate<NameTag> = html`

  <div>${x => x.greeting}</div>

`;

理解绑定

我们已经了解了如何使用箭头函数来声明模板的动态部分。让我们再看几个例子,看看你能得到什么。

内容

要绑定元素的内容,只需在元素的开始和结束标记中提供表达式。它可以是元素的唯一内容,也可以与其他元素和文本交织在一起。

实例: 基础文本内容

<h3>${x => x.greeting.toUpperCase()}</h3>

实例: 插入文本内容

<h3>${x => x.greeting}, my name is ${x => x.name}.</h3>

实例: 异构内容

<h3>

  ${x => x.greeting}, my name is

  <span class="name">${x => x.name}</span>.

</h3>

出于安全原因,动态内容通过 textContent HTML属性设置。你不能这样设置HTML内容。有关设置HTML的显式选择加入机制,请参见下文。

属性

还可以使用表达式设置HTML元素的属性值。只需将表达式放在HTML属性的值所在的位置。然后,模板引擎将使用 setAttribute(...),无论何时需要更新。此外,有些属性被称为boolean attributes(例如,必填、只读、禁用)。这些属性的行为与普通属性不同,需要特殊的值处理。如果在属性名前面加上?,模板引擎将为您处理这个问题。

实例: 基本属性值

<a href="${x => x.aboutLink}">About</a>

实例: 插入属性值

<a href="products/${x => x.id}">

  ${x => x.name}</a>

<li class="list-item ${x => x.type}">

  ...</li>

当绑定到class时,底层引擎不会重写通过其他机制添加到元素中的类。它只添加和删除直接由绑定产生的类。这种“默认安全”的行为确实会带来轻微的性能代价。要退出此功能并通过总是覆盖所有类来挤出每一盎司的性能,请在className属性上使用属性绑定(见下文)。

例如 :className="list-item ${x => x.type}"

<span style="text-decoration: ${x => x.done ? 'line-through' : ''}">

  ${x => x.description}</span>

实例: ARIA 属性

<div role="progressbar"

     aria-valuenow="${x => x.value}"

     aria-valuemin="${x => x.min}"

     aria-valuemax="${x => x.max}">

</div>

实例: Boolean 属性

<button type="submit" ?disabled="${x => !x.enabled}">Submit</button>

实例: 空值

在某些情况下,属性在使用时需要有值,但如果未使用则不存在。这些属性与布尔属性不同,因为它们的存在单独触发了它们的效果。在这种情况下,可能会提供一个空值(nullundefined),以便该属性在该条件下不存在。

<div aria-hidden="${x => x.isViewable ? 'true' : null}"></div>

特性

特性也可以直接在HTML元素上设置。为此,请在特性名称前面加上 :以指示属性绑定。然后,模板引擎将使用表达式指定元素的属性值。 实例: 基本特性值

<my-element :myCustomProperty="${x => x.mySpecialData}">

  ...</my-element>

实例: Inner HTML

<div :innerHTML="${x => x.someDangerousHTMLContent}"></div>

避免需要直接设置HTML的情况,尤其是当内容来自外部源时。如果必须这样做,则应始终清理HTML。

完成HTML清理的最佳方法是配置受信任类型策略使用HIElement的模板引擎。HIElement确保所有HTML字符串都通过配置的策略。此外,通过利用平台的trusted types功能,您可以通过CSP头获得策略的本机强制执行。下面是一个如何配置自定义策略来清理HTML的示例:

import { DOM } from '@hi-element';

const myPolicy = trustedTypes.createPolicy('my-policy', {

  createHTML(html) {

    // TODO: invoke a sanitization library on the html before returning it

    return html;

  }

});

DOM.setHTMLPolicy(myPolicy);

出于安全原因,HTML策略只能设置一次。因此,它应该由应用程序开发人员而不是组件作者设置,并且应该在应用程序的启动序列中立即设置。

Events

除了呈现内容、属性和属性外,您通常还需要添加事件侦听器,并在事件触发时执行代码。为此,请在事件名称前面加上 @,并提供在该事件触发时要调用的表达式。在事件绑定中,您还可以访问一个特殊的context参数,从中可以访问event 对象。

实例: Basic Events

<button @click="${x => x.remove()}">Remove</button>

实例: Accessing Event Details

<input type="text"

       :value="${x => x.description}"

       @input="${(x, c) => x.handleDescriptionChange(c.event)}">

在上述两个示例中,在执行事件处理程序后,默认情况下会对事件对象调用preventDefault()。您可以从处理程序返回true 以选择退出此行为。

第二个示例演示了模板引擎的一个重要特性:它只支持单向数据流(model=>view)。它不支持双向数据绑定(模型<=>视图)。如上所示,将数据从视图推回到模型应该通过调用模型API的显式事件来处理。

模板和元素生命周期

在自定义元素生命周期的connectedCallback阶段,HIElement创建模板并绑定结果视图。模板的创建仅在元素第一次连接时发生,而绑定则在元素每次连接时发生(为了对称,在disconnectedCallback期间解除绑定)。

在未来,我们正在计划新的优化,这将使我们能够安全地确定何时不需要解除绑定/重新绑定某些视图。

在大多数情况下,HIElement呈现的模板由自定义元素配置的template属性决定。但是,也可以在名为resolveTemplate()的自定义元素类上实现一个方法,该方法返回一个模板实例。如果存在此方法,将在connectedCallback期间调用它以获取要使用的模板。这允许元素作者根据连接时元素的状态动态选择完全不同的模板。

除了在connectedCallback期间进行动态模板选择之外,HIElement$hiController属性还可以通过将控制器的template属性设置为任何有效模板,随时动态更改模板。

请参照

github.com/hi-kits/hi-…