面向 Polymer 用户的 Lit 指南
Lit 是 Polymer 库的继任者。如果你有一个使用 Polymer 构建的项目并想将其迁移到 Lit,或者你熟悉 Polymer 并想了解它与 Lit 的区别,那么这篇文章正是你需要的。
本文档提供了 Lit 与 Polymer 关系的快速概述,并提供了一份指南,展示常见的 Polymer 代码如何转换为 Lit 代码。
Lit 与 Polymer 有什么关系?
Permalink to "Lit 与 Polymer 有什么关系?"Polymer 是最早的 Web 组件构建库之一。Lit 是 Polymer 的继任者,由同一团队打造,并具有许多相同的目标。这两个项目有许多共同目标,但 Lit 利用了在 Polymer 开发过程中获得的经验教训。
因为 Polymer 是 Lit 的前身,两者之间有很多相似之处。这两个库都能让你轻松构建像原生 HTML 元素一样工作的组件,并且都支持声明式的 HTML 模板。
Lit 在以下几个方面与 Polymer 不同:
Lit 的渲染默认是异步且批处理的。 除少数例外,Polymer 的所有更新都是同步的。
Lit 提供了一个更新生命周期,为观察属性变化和计算派生值提供了强大的机制。 Polymer 具有声明式的观察者和计算属性,但很难预测观察者的运行顺序。
Lit 专注于 JavaScript 优先的开发方式,使用原生 JavaScript 模块。 Polymer 最初 专注于 HTML 优先的开发方式,这得益于 HTML Imports 规范,但该规范已从 Web 平台中移除。
Lit 的表达式使用标准 JavaScript。 Polymer 在其绑定中使用了一种有限的领域特定语言。因为 Lit 使用标准 JavaScript,所以你还可以在表达式中使用 JavaScript 进行控制流(条件模板和重复模板),而 Polymer 则使用专门的辅助元素。
Lit 旨在提供一个简单、易于理解的心智模型,采用单向数据流。 Polymer 支持双向数据绑定和观察者,这在简单项目中可能很好用,但随着项目变得更复杂,往往会增加数据流理解的难度。
从 Polymer 迁移到 Lit
Permalink to "从 Polymer 迁移到 Lit"如果你计划从 Polymer 迁移到 Lit,不必一次全部完成。Polymer 和 Lit 可以协同工作,因此你可以逐个迁移组件。
在开始使用 Lit 之前,你需要做以下几件事来更新你的项目:
将你的项目更新到 Polymer 3。
确保你的项目工具链支持更新的 JavaScript 特性。
移除双向绑定。
更新到 Polymer 3
Permalink to "更新到 Polymer 3"Polymer 2.x 及更早版本使用 HTML Imports,该规范已从 Web 平台中移除。Polymer 3 和 Lit 都以 JavaScript 模块的形式分发,这意味着它们可以很好地协同工作,并能利用各种现代 Web 工具链。
在大多数情况下,迁移过程的大部分可以使用 Polymer modulizer 工具自动完成。更多信息请参阅 Polymer 3.0 升级指南。
Polymer 使用 ECMAScript 2015 版本 JavaScript 规范的特性。如果你是从某个 Polymer 入门套件开始的,你的工具链可能不支持更新的 JavaScript。
Lit 使用 ECMAScript 2019 的特性(一些 Lit 示例代码可能包含更新的语言特性)。如果你的工具不能处理这些更新的 JavaScript 特性,你需要升级它们。例如:
公共实例字段和公共静态字段。这些在示例代码中被广泛使用:
static styles = [ css`:host { display: block; }` ];Async 和 await,可用于简化基于 Promise 的代码。例如:
async _loginClickHandler() {this.loggedIn = true;// 等待 `loggedIn` 状态渲染到 DOMawait this.updateComplete;this.dispatchEvent(new Event('login'));}
有关 Lit 语言要求的更多信息,请参阅要求。
移除双向绑定
Permalink to "移除双向绑定"Polymer 的双向绑定实际上将宿主属性与子元素属性紧密耦合。这种紧密耦合会产生许多问题,因此团队选择不在 Lit 中添加双向绑定。
在迁移到 Lit 之前移除双向绑定将降低迁移的复杂性,并让你在开始迁移之前在 Polymer 中测试不使用双向绑定的应用程序。
如果你的应用程序不使用双向绑定,可以跳过本节。
双向绑定的问题
Permalink to "双向绑定的问题"对于双向绑定,Polymer 使用自己的协议,该协议有三个主要组成部分:
从宿主到子元素的绑定。
处理来自子元素的属性变化事件的事件监听器。
子元素在属性变化时自动触发变更事件的机制(
notify: true)。
这最后一点是最有问题的。组件会对属性的任何变化触发变更事件,每个变更事件都是同步处理的。在整个应用程序中广泛使用双向绑定会使数据流和组件更新顺序难以理解。
理想情况下,事件是一个离散的信号,用于传达一个不容易被其他方式观察到的明确变化。像 Polymer 那样将事件作为设置属性的副作用发送,会使通信变得可能冗余和隐式。这种隐式行为尤其会使数据流难以理解。
总结一下自定义元素最佳实践指南,组件应该在以下情况下触发事件:
当元素的状态因用户交互而发生变化时——比如点击按钮或编辑组件内的文本字段。
当组件内部发生变化时——比如定时器触发或动画完成。
理想情况下,组件应该触发_语义化_事件,描述什么发生了变化,而不是让底层 UI 事件冒泡出去。例如,一个让用户更新个人资料数据的表单,当用户点击完成按钮时可能会触发一个 profile-updated 事件。profile-updated 事件对父组件是相关的:而 click 事件则不是。
替换双向绑定
Permalink to "替换双向绑定"替换双向绑定有很多方法。如果你只是想将现有的 Polymer 应用程序迁移到 Lit,你可能只需要用具有类似功能的代码替换双向绑定:
移除自动属性变更事件(
notify: true)。用更有意图的变更事件替换自动属性变更事件。
用单向绑定和事件处理器替换双向绑定注解。(此步骤是可选的,但可以使向 Lit 的过渡更简单。)
例如,如果子元素因为用户交互而更新通知属性,请移除 notify: true,并根据用户交互事件手动触发变更事件。
考虑以下 Polymer 代码:
static get properties() { return { name: { type: String, notify: true } };}
static get template() { return html` Name: [[name]]<br> Enter name: <input value={{name::input}}> `;} 该组件对 input 元素使用了双向绑定,并具有一个通知属性,因此父元素可以对 name 属性使用双向绑定。对输入元素的双向绑定是有意义的,因为绑定只在 input 事件触发时设置。然而,通过为 input 事件添加事件监听器,你可以消除通知属性:
static properties = { name: { type: String, }}
static get template() { return html` Name: [[name]]<br> Enter name: <input on-input="inputChanged" value=[[name]]> `;}
inputChanged(e) { this.name = e.target.value; const propChangeEvent = new CustomEvent('name-changed', { detail: { value: this.name } }); this.dispatchEvent(propChangeEvent);} 这段代码显式地监听 input 事件并触发 name-changed 事件。这段代码可以与期望 Polymer 双向绑定协议的父组件一起工作,因此你可以逐个更新组件。
当父组件设置 name 属性时,这段代码不会触发属性变更事件——这是好事。而且你可以相当直接地将这段代码迁移到 Lit。
使用这样的事件处理器,你还可以为子组件添加逻辑——例如仅在用户停止输入特定时间后才触发 name-changed 事件。
单向数据流。
另一种替代方案是用单向数据流模式替换双向绑定,使用像 Redux 这样的状态容器。各个组件可以订阅状态更新,并分发操作来更新状态。我们推荐在新开发中使用这种方式,但如果你已经有一个基于双向绑定的应用程序,可能需要更多工作。
Polymer 到 Lit 指南
Permalink to "Polymer 到 Lit 指南"本节展示 Polymer 代码如何处理常见任务,并展示等效的 Lit 代码。如果你已经熟悉 Polymer 并想学习 Lit,这会很有帮助;或者你正在将现有项目从 Polymer 迁移到 Lit。
本节假设你正在使用 Polymer 3.0。如果你正在将项目从 Polymer 迁移到 Lit,你需要先迁移到 Polymer 3.0。
有关迁移现有项目的更多信息,请参阅从 Polymer 迁移到 Lit。
JavaScript 还是 TypeScript?
Lit 两者都能很好地支持。大多数 Lit 示例使用可切换的代码示例组件展示,因此你可以选择 TypeScript 或 JavaScript 语法。
Polymer 和 Lit 都基于 Web 组件,因此在这两个库中定义组件看起来非常相似。在最简单的情况下,唯一的区别是基类。
Polymer:
import {PolymerElement} from '@polymer/polymer/polymer-element.js';
export class MyElement extends PolymerElement { /* ... */ }customElements.define('my-element', MyElement); Lit:
import {LitElement} from 'lit';import {customElement} from 'lit/decorators.js';
customElement('my-element')export class MyElement extends LitElement { /* ... */ } import {LitElement} from 'lit';
export class MyElement extends LitElement { /* ... */ }customElements.define('my-element', MyElement); 关于装饰器
Permalink to "关于装饰器"Lit 提供了一组装饰器来改善你的开发体验——比如上一节 TypeScript 示例中展示的 customElement。请注意,本站点上的 TypeScript 示例包含装饰器,而 JavaScript 示例则省略了它们,但实际上你可以在 JavaScript 中使用装饰器,也可以在 TypeScript 中不使用装饰器。这取决于你自己。在 JavaScript 中使用装饰器需要像 Babel 这样的编译器。(由于你已经需要 TypeScript 编译器,只需配置它来处理装饰器即可。)更多信息请参阅装饰器。
DOM 模板
Permalink to "DOM 模板"Polymer 3 和 Lit 都提供了 html 标签函数来定义模板。
Polymer:
import {PolymerElement, html} from '@polymer/polymer/polymer-element.js';
export class MyElement extends PolymerElement { static get template() { return html`<b>Hello</b>`; }}customElements.define('my-element', MyElement); Lit:
import {LitElement, html} from 'lit';import {customElement} from 'lit/decorators.js';
customElement('my-element')export class MyElement extends LitElement { render() { return html`<b>Hello</b>`; }} import {LitElement, html} from 'lit';
export class MyElement extends LitElement { render() { return html`<b>Hello</b>`; }}customElements.define('my-element', MyElement); 请注意,Lit 的 html 与 Polymer 的 html 不同。它们的基本目的相同,但工作方式不同。Polymer 的 html 在元素初始化期间只调用一次。Lit 的 html 通常在每次更新时调用。设置工作每个模板字面量字符串只执行一次,因此后续对 Lit 的 html 函数的增量更新调用非常快。
有关 Lit 模板的更多信息,请参阅模板概述。
Polymer 组件通常直接在模板中包含样式。
static get template() { return html` <style> .fancy { color: blue; } </style> ... `;} 在 Lit 中,你_通常_在静态 styles 字段中使用 css 标签函数提供样式。
import {LitElement, css, html} from 'lit'; ...
static styles = css`.fancy { color: blue; }`; 也支持像 Polymer 那样直接在模板中添加 style 标签:
import {LitElement, html} from 'lit'; ... render() { return html` <style> .fancy { color: blue; } </style> ... } 使用 style 标签的性能可能比静态 styles 字段略低,因为样式是每个实例评估一次而不是每个类评估一次。
有关更多信息,请参阅样式。
Polymer 有数据绑定,而 Lit 在模板中有_表达式_。Lit 表达式是绑定到模板中特定位置的标准 JavaScript 表达式。因此,Lit 表达式几乎可以完成你用 Polymer 数据绑定能做的所有事情,还能做很多在 Polymer 中不容易做到的事情。
双向绑定。
Lit 团队有意选择不实现双向数据绑定。虽然这个特性看起来能简化一些常见需求,但实际上它使数据流和组件更新顺序难以理解。我们建议在迁移到 Lit 之前移除双向绑定。更多信息请参阅移除双向绑定。
Polymer:
import { PolymerElement, html } from '@polymer/polymer/polymer-element.js';
class UserView extends PolymerElement { static get properties() { return { name: String }; }
static get template() { return html` <div>[[name]]</div> `; }} Lit:
import {html, LitElement} from 'lit';import {property} from 'lit/decorators.js';
class UserView extends LitElement {
property() name: string;
render() { return html` <div>${this.name}</div> `; }} import {html, LitElement} from 'lit';
class UserView extends LitElement {
static properties = { name: {} }
render() { return html` <div>${this.name}</div> `; }} 如你所见,主要区别是将 Polymer 绑定中的双括号:
[[expression]]
替换为标签模板字面量的表达式语法:
${expression}
另外请注意,Lit 表达式使用 this.name 而不是直接用 name。Polymer 绑定只允许在绑定注解中使用特定内容,比如属性名或路径(如 user.id 或 shapes[4].type)。属性名或路径相对于当前的绑定作用域进行求值。
你可以在 Lit 中使用任何标准 JavaScript 衝达式,标准 JavaScript 作用域规则同样适用。例如,你可以访问在 render() 方法内创建的局部变量,或使用 this 访问实例变量。
render() { let count = this.getMatchingItems(this.pattern);
return html`You have ${count} matches.`} 与 Polymer 类似,Lit 支持使用表达式设置属性、attribute 和事件处理器。Lit 使用略有不同的语法,使用前缀而非后缀。下表总结了 Polymer 和 Lit 绑定语法之间的差异:
| 类型 | Polymer | Lit |
|---|---|---|
| 属性 | property-name=[[value]] | .propertyName=${value} |
| Attribute | attribute-name$=[[value]] | attribute-name=${value} |
| 布尔 attribute | attribute-name?=[[value]] | ?attribute-name=${value} |
| 事件 | on-event-name$=[[handler]] | @event-name=${handler} |
注意事项:
属性表达式。Lit 使用字面属性名,以句点作为前缀。Polymer 使用相应的 attribute 名。
事件处理器。在 Lit 中,处理器可以是一个方法,如
${this.clickHandler},也可以是一个箭头函数。使用箭头函数,你可以闭包其他数据或调用具有不同签名的函数。例如:<input @change=${(e) => this.setValue(e.target.value)}>
更多信息请参阅表达式。
通常,我们建议在将 Polymer 项目迁移到 Lit 之前移除双向绑定。更多信息请参阅移除双向绑定。
如果你已经用单向绑定和事件监听器替换了双向绑定,你应该能够将它们相当直接地迁移到 Lit。
如果你正在编写一个 Lit 组件来替换以前使用双向绑定与 Polymer 子组件通信的父组件,请添加属性表达式来设置属性,并添加事件监听器来处理 property-changed 事件。
Polymer:
static get template() { return html` <polymer-child childprop="[[parentprop]]" on-childprop-changed="childpropChanged"> </polymer-child> `;}
childpropChanged(e) { this.parentprop = e.detail.value;} Lit:
static template = html` <polymer-child .childprop=${this.parentprop} @childprop-changed=${(e) => this.parentprop = e.detail.value}> </polymer-child>`; Polymer 使用 dom-if 辅助元素来支持条件渲染。
<template is="dom-if" if=""> <div>condition is true</div></template> 在 Lit 中,你可以使用 JavaScript 条件表达式。条件运算符(或三元运算符)效果很好:
html`${this.condition ? html`<div>condition is true</div>` : ''}`; 与 Polymer 的 dom-if 不同,条件运算符允许你为 true 和 false 条件都提供内容,不过你也可以如示例中那样返回空字符串来不渲染任何内容。
更多信息请参阅条件渲染。
隐藏或重新创建 DOM
Permalink to "隐藏或重新创建 DOM"默认情况下,Polymer 的 dom-if 与 Lit 的条件渲染行为略有不同。当条件从真值变为假值时,dom-if 只是_隐藏_条件 DOM,而不是将其从 DOM 树中移除。当条件再次变为真值时,这可能会节省一些资源。
将 Polymer 的 dom-if 迁移到 Lit 时,你有几个选择:
使用简单的 JavaScript 条件表达式。当条件变为假值时,Lit 会移除并丢弃条件 DOM。如果你将
restamp属性设置为true,dom-if也会做同样的事情。使用标准的
hidden属性来隐藏内容而不将其从页面中移除。<header hidden=${this.headerHidden}>这非常轻量。但是,即使条件为 false,DOM 也会在首次渲染时创建。
用
cache指令包裹条件表达式,以避免在条件变化时丢弃和重新创建 DOM。
在大多数情况下,简单的条件表达式效果很好。如果条件 DOM 大而复杂,并且你观察到当条件切换为 true 时重新创建 DOM 有延迟,你可以使用 Lit 的 cache 指令来保留条件 DOM。使用 cache 时,DOM 仍然会从树中移除,但会被缓存在内存中,当条件变化时可以节省资源。
import {LitElement, html} from 'lit';import {cache} from 'lit/directives/cache.js';...
return html`${cache(this.condition ? html`<div>condition is true</div>` : '' )}`; 由于当条件为假时这不会渲染任何内容,你可以用它来避免在初始页面加载时创建复杂的 DOM。
Polymer 使用 dom-repeat 辅助元素来实现模板重复。
<template is="dom-repeat" items=""> <li></li></template> dom-repeat 内的模板只能访问有限的属性:宿主元素上的属性,以及 dom-repeat 添加的 item 和 index 属性。
与条件渲染类似,Lit 可以使用 JavaScript 处理重复,让表达式返回一个值数组。Lit 的 map 指令类似于 map() 数组方法,只是它还接受其他类型的可迭代对象,如 Set 或 Generator。
import {map} from 'lit/directives/map.js';... render() { return html` <ul> ${map(this.items, (item) => html`<li>${item.name}</li>`) } </ul> `; }Polymer 绑定只能访问特定属性,而 Lit 表达式可以访问 JavaScript 作用域中的任何内容。
你也可以在 render() 方法中生成数组:
render() { const itemTemplates = []; for (i of this.items) { itemTemplates.push(html`<li>${i}</li>`); }
return html` <ul> ${itemTemplates} </ul> `;} 处理来自重复模板的事件
有多种方法可以为重复元素添加事件监听器:
如果事件冒泡,你可以使用_事件委托_,在父元素上添加单个监听器。
如果事件不冒泡,你可以为每个重复元素添加监听器。
有关使用这两种技术的示例,请参阅事件部分中的监听从重复模板触发的事件。
当处理来自重复模板生成的元素的事件时,你经常需要识别触发事件的元素以及生成该元素的数据。
Polymer 通过向事件对象添加数据来帮助后者。
Lit 不会添加这些额外数据,但你可以为每个重复元素附加一个唯一值以便引用:
render() { return html` <div @click=${this.handleClick}> ${map(this.items, (item) => { return html`<button data-id=${item.id}>${item.text}</button>` } </div> `;} 当为单个项目添加监听器时,你也可以使用箭头函数直接将数据传递给事件处理器:
render() { return html` <div> ${this.items.map((item) => { return html`<button @click=${() => this._handleClick(item)}> ${item.text} </button>`; } } </div> `;} Lit 的_响应式属性_与 Polymer 的声明属性相当匹配。响应式属性支持与声明属性相同的许多功能,包括在属性和 attribute 之间同步值。
与 Polymer 类似,Lit 允许你使用静态 properties 字段配置属性。
Lit 还支持使用 @property 装饰器来声明响应式属性。更多信息请参阅关于装饰器。
Polymer:
static get properties() { return { user: String, count: { type: Number, notify: true } } } Lit:
property()user: string;
property({type: Number})count: number; static properties = { user: {}, count: { type: Number }} Polymer 和 Lit 在声明属性时都支持多个选项。以下列表展示了 Polymer 选项及其 Lit 等价物。
typeLit 的
type选项具有相同的用途。valueLit 不支持这样设置属性的值。请在构造函数中设置默认值。如果使用
@property装饰器,你也可以使用类字段初始化器。reflectToAttribute在 Lit 中,这个选项被简写为
reflect。readOnlyLit 不包含对只读响应式属性的内置支持。如果你需要将计算属性作为组件公共 API 的一部分,可以添加一个没有 setter 的 getter。
notify此功能用于支持双向绑定。由于双向绑定的问题中描述的问题,Lit 没有实现它。
Lit 组件可以使用原生 Web API(如
dispatchEvent)来响应用户输入或内部状态变化触发事件。computedLit 不支持声明式的计算属性。请参阅计算属性以了解替代方案。
observerLit 不直接支持观察者。相反,它在生命周期中提供了多个可重写的点,你可以在这些点根据变化的属性采取行动。
静态 getter 与静态类字段
Permalink to "静态 getter 与静态类字段"请注意,Polymer 示例使用 getter——static get properties()——而 Lit JavaScript 示例使用类字段——static properties。这两种形式的工作方式相同。当 Polymer 3 发布时,类字段的支持还不广泛,因此 Polymer 示例代码使用了 getter。
如果你要将 Lit 组件添加到现有的 Polymer 应用程序中,你的工具链可能不支持类字段格式。在这种情况下,你可以使用静态 getter。
static get properties() { return { user: {}, count: { type: Number } }} 数组和对象类型的属性
Permalink to "数组和对象类型的属性"与 Polymer 类似,Lit 在属性变化时进行脏检查以避免执行不必要的工作。如果你有一个持有对象或数组的属性,这可能会导致问题。如果你修改了对象或数组,Lit 不会检测到变化。
在大多数情况下,避免这些问题的最佳方式是使用不可变数据模式,这样你总是分配新的对象或数组值,而不是修改现有的对象或数组。Polymer 通常也是如此。
Polymer 包含用于可观察地设置子属性和修改数组的 API,但正确使用它们有一定难度。如果你正在使用这些 API,你可能需要迁移到不可变数据模式。
有关更多信息,请参阅修改对象和数组属性。
只读属性在 Polymer 中不是一个非常常用的功能,如果需要的话实现起来也不难,因此没有包含在 Lit API 中。
要将内部状态值作为组件公共 API 的一部分暴露出来,你可以添加一个没有 setter 的 getter。你可能还想在状态变化时触发事件(例如,一个从网络加载资源的组件可能有一个"加载中"状态,并在该状态变化时触发事件)。
Polymer 的只读属性包含一个隐藏的 setter。如果对你的组件有意义,你可以为属性添加一个私有 setter。
private _name: string = 'Somebody';
property({attribute: false})get name() { return this._name; }
private _setName(name: string) { this._name = name; this.requestUpdate('name');} static properties = { name: {attribute: false}};
constructor() { super(); this._name = 'Somebody';}
get name() { return this._name }
_setName(name) { this._name = name; this.requestUpdate('name');} 请注意,如果属性没有包含在组件的模板中,你不需要声明它(使用 @property 或 static properties)或调用 requestUpdate()。
计算属性和观察者
Permalink to "计算属性和观察者"Lit 提供了多个可重写的生命周期方法,这些方法在响应式属性变化时被调用。使用这些方法来实现在 Polymer 中会放在计算属性或观察者中的逻辑。
以下列表总结了如何迁移不同类型的计算属性和观察者。
计算属性:
对于在模板中临时使用的值,在
render()中将值计算为局部变量,并在返回之前在模板中使用它们。对于需要持久存储或计算成本较高的值,在
willUpdate()中进行。使用
changedProperties.has()方法仅在依赖变化时才计算,以避免每次更新都进行昂贵的重新计算。
观察者:
如果观察者需要基于属性变化直接操作 DOM,请使用
updated()。这在render()回调之后被调用。否则,使用
willUpdate()。无论哪种情况,都使用
changedProperties.has()来了解属性何时发生了变化。
基于路径的观察者:
这些是观察像
foo.bar或foo.*这样的路径的复杂观察者:observers: ['fooBarChanged(foo.bar)', 'fooChanged(foo.*)']此功能非常特定于 Polymer 的数据系统,在 Lit 中没有等价物。
我们建议使用不可变模式作为一种更具互操作性的方式来传达深层属性变化。有关更多信息,请参阅修改对象和数组属性。
在 render() 中计算临时值
Permalink to "在 render() 中计算临时值"如果你需要计算仅用于渲染的临时值,你可以直接在 render() 方法中计算它们。
Polymer:
static get template() { return html` <p>Text: <span></span></p> `; }
static get properties() { return { /* ... */ cyphertext: { type: String, computed: 'rot13(cleartext)' } } } Lit:
render() { const cyphertext = rot13(this.cleartext); return html`Text: ${cyphertext}`;} 此外,由于 Lit 允许你在模板中使用任何 JavaScript 表达式,Polymer 的计算绑定可以被内联表达式替换,包括函数调用。
Polymer:
static get template() { return html` My name is <span>[[_formatName(given, family)]]</span> `; }
Lit:
render() { html` My name is <span>${this._formatName(given, family)}</span> `;} 由于 Lit 表达式是普通 JavaScript,你需要在表达式中使用 this 来访问实例属性或方法。
在 willUpdate() 中计算属性
Permalink to "在 willUpdate() 中计算属性"willUpdate() 回调是基于其他属性值计算属性的理想位置。willUpdate() 接收一个已更改属性值的映射,因此你可以处理当前的变化。
willUpdate(changedProperties: PropertyValues<this>) { if (changedProperties.has('userId') || changedProperties.has('avatarId')) { this.imageUrl = this._getImageUrl(this.userId, this.avatarId); } } willUpdate(changedProperties) { if (changedProperties.has('userId') || changedProperties.has('avatarId')) { this.imageUrl = this._getImageUrl(this.userId, this.avatarId); } } 使用 willUpdate() 可以让你根据完整的已更改属性集合来决定做什么。这避免了多个观察者或计算属性以不可预测方式交互的问题。
混入是将可复用功能打包用于 Polymer 组件的几种方式之一。如果你正在将 Polymer 混入移植到 Lit,你有几个选项。
独立函数。由于 Polymer 的数据绑定只能访问实例成员,人们经常创建混入仅仅是为了在数据绑定中使函数可用。在 Lit 中这不是必需的,因为你可以在模板中使用任何 JavaScript 表达式。你可以从另一个模块导入函数,直接在模板中使用,而不需要使用混入。
Lit 混入。Lit 混入的工作方式与 Polymer 混入非常相似,因此许多 Polymer 混入可以重新实现为 Lit 混入。更多信息请参阅混入。
响应式控制器。响应式控制器是打包可复用功能的另一种方式。有关比较混入和响应式控制器的更多信息,请参阅控制器和混入。
Lit 组件具有与 Polymer 组件相同的原生 Web 组件生命周期回调集。
此外,Lit 组件还有一组可用于自定义响应式更新周期的回调。
如果你在 Polymer 元素中使用了 ready() 回调,你可能可以使用 Lit 的 firstUpdated() 回调达到相同的目的。firstUpdated() 回调在组件的 DOM 首次渲染后被调用。例如,如果你想聚焦到渲染 DOM 中的某个元素,可以使用它。
firstUpdated() { this.renderRoot.getElementById('my-text-area').focus();} 有关更多信息,请参阅完成更新。
