响应式控制器
响应式控制器是一个可以连接到组件 响应式更新周期 的对象。控制器可以将与某个功能相关的状态和行为捆绑在一起,使其在多个组件定义之间可重用。
您可以使用控制器实现需要自身状态和访问组件生命周期的功能,例如
- 处理全局事件,如鼠标事件
- 管理异步任务,如通过网络获取数据
- 运行动画
响应式控制器允许您通过组合并非本身就是组件的更小的部分来构建组件。可以将它们视为可重用、部分组件定义,拥有自己的标识和状态。
响应式控制器在许多方面类似于类 mixin。主要区别在于它们有自己的标识,不会添加到组件的原型中,这有助于限定它们的 API,并让您可以在每个宿主组件中使用多个控制器实例。有关更多详细信息,请参见 控制器和 mixin。
使用控制器
“使用控制器”的永久链接每个控制器都有自己的创建 API,但通常您会创建一个实例并将它存储在组件中
class MyElement extends LitElement { private clock = new ClockController(this, 1000);}与控制器实例关联的组件称为宿主组件。
控制器实例注册自身以接收来自宿主组件的生命周期回调,并在控制器有新数据要渲染时触发宿主更新。这就是 ClockController 示例定期渲染当前时间的方式。
控制器通常会公开一些要在宿主 render() 方法中使用的功能。例如,许多控制器将具有一些状态,例如当前值
render() { return html` <div>Current time: ${this.clock.value}</div> `; }由于每个控制器都有自己的 API,因此请参考特定控制器的文档以了解如何使用它们。
编写控制器
“编写控制器”的永久链接响应式控制器是与宿主组件关联的对象,它实现一个或多个宿主生命周期回调或与宿主交互。它可以通过多种方式实现,但我们将重点介绍使用 JavaScript 类,使用构造函数进行初始化,使用方法进行生命周期。
控制器初始化
“控制器初始化”的永久链接控制器通过调用 host.addController(this) 向其宿主组件注册自身。通常,控制器会存储对其宿主组件的引用,以便它以后可以与宿主组件交互。
class ClockController implements ReactiveController { private host: ReactiveControllerHost;
constructor(host: ReactiveControllerHost) { // Store a reference to the host this.host = host; // Register for lifecycle updates host.addController(this); }}class ClockController { constructor(host) { // Store a reference to the host this.host = host; // Register for lifecycle updates host.addController(this); }}您可以添加其他构造函数参数以进行一次性配置。
class ClockController implements ReactiveController { private host: ReactiveControllerHost; timeout: number
constructor(host: ReactiveControllerHost, timeout: number) { this.host = host; this.timeout = timeout; host.addController(this); }class ClockController { constructor(host, timeout) { this.host = host; this.timeout = timeout; host.addController(this); }控制器向宿主组件注册后,您就可以将生命周期回调以及其他类字段和方法添加到控制器以实现所需的状态和行为。
生命周期
“生命周期”的永久链接响应式控制器生命周期在 ReactiveController 接口中定义,是响应式更新周期的子集。LitElement 在其生命周期回调期间调用任何已安装的控制器。这些回调是可选的。
hostConnected():- 在宿主连接时调用。
- 在创建
renderRoot之后调用,因此此时将存在 shadow root。 - 对于设置事件侦听器、观察者等很有用。
hostUpdate():- 在宿主的
update()和render()方法之前调用。 - 对于在 DOM 更新之前读取 DOM 很有用(例如,用于动画)。
- 在宿主的
hostUpdated():- 在更新之后、宿主的
updated()方法之前调用。 - 对于在 DOM 修改之后读取 DOM 很有用(例如,用于动画)。
- 在更新之后、宿主的
hostDisconnected():- 在宿主断开连接时调用。
- 对于清除
hostConnected()中添加的内容(如事件侦听器和观察者)很有用。
有关更多信息,请参见 响应式更新周期。
控制器宿主 API
“控制器宿主 API”的永久链接响应式控制器宿主实现了一个用于添加控制器和请求更新的小型 API,并负责调用其控制器的生命周期方法。
这是控制器宿主上公开的最小 API
addController(controller: ReactiveController)removeController(controller: ReactiveController)requestUpdate()updateComplete: Promise<boolean>
您还可以创建特定于 HTMLElement、ReactiveElement、LitElement 并需要更多这些 API 的控制器;甚至创建与特定元素类或其他接口绑定的控制器。
LitElement 和 ReactiveElement 是控制器宿主,但宿主也可以是其他对象,例如来自其他 Web 组件库的基类、来自框架的组件或其他控制器。
从其他控制器构建控制器
“从其他控制器构建控制器”的永久链接控制器也可以由其他控制器组成。为此,请创建一个子控制器并将宿主转发给它。
class DualClockController implements ReactiveController { private clock1: ClockController; private clock2: ClockController;
constructor(host: ReactiveControllerHost, delay1: number, delay2: number) { this.clock1 = new ClockController(host, delay1); this.clock2 = new ClockController(host, delay2); }
get time1() { return this.clock1.value; } get time2() { return this.clock2.value; }}class DualClockController { constructor(host, delay1, delay2) { this.clock1 = new ClockController(host, delay1); this.clock2 = new ClockController(host, delay2); }
get time1() { return this.clock1.value; } get time2() { return this.clock2.value; }}控制器和指令
“控制器和指令”的永久链接将控制器与指令结合使用可以成为一项非常强大的技术,尤其是在需要在渲染之前或之后执行操作的指令(如动画指令)或需要对模板中特定元素的引用的控制器中。
使用控制器和指令主要有两种模式
- 控制器指令。这些指令本身是控制器,以便钩入宿主生命周期。
- 拥有指令的控制器。这些控制器会创建一个或多个指令供宿主模板使用。
有关编写指令的更多信息,请参见 自定义指令。
控制器指令
“控制器指令”的永久链接响应式控制器不需要作为宿主上的实例字段存储。使用 addController() 添加到宿主的任何内容都是控制器。特别是,指令也可以是控制器。这使指令能够钩入宿主生命周期。
拥有指令的控制器
“拥有指令的控制器”的永久链接指令不需要是独立函数,它们也可以是其他对象(如控制器)上的方法。这在控制器需要对模板中特定元素的特定引用的情况下很有用。
例如,想象一个 ResizeController,它允许您使用 ResizeObserver 观察元素的大小。为了使其正常工作,我们需要一个 ResizeController 实例,以及一个放置在我们要观察的元素上的指令
class MyElement extends LitElement { private _textSize = new ResizeController(this);
render() { return html` <textarea ${this._textSize.observe()}></textarea> <p>The width is ${this._textSize.contentRect?.width}</p> `; }}class MyElement extends LitElement { _textSize = new ResizeController(this);
render() { return html` <textarea ${this._textSize.observe()}></textarea> <p>The width is ${this._textSize.contentRect?.width}</p> `; }}为了实现这一点,您创建一个指令并从方法中调用它
class ResizeDirective { /* ... */}const resizeDirective = directive(ResizeDirective);
export class ResizeController { /* ... */ observe() { // Pass a reference to the controller so the directive can // notify the controller on size changes. return resizeDirective(this); }}待办事项
- 查看并清理此示例
响应式控制器非常通用,具有非常广泛的潜在用例。它们特别适合将组件连接到外部资源,如用户输入、状态管理或远程 API。以下是一些常见的用例。
外部输入
“外部输入”的永久链接响应式控制器可用于连接到外部输入。例如,键盘和鼠标事件、调整大小观察器或变动观察器。控制器可以提供输入的当前值以用于渲染,并在值更改时请求宿主更新。
示例:MouseMoveController
“示例:MouseMoveController”的永久链接此示例展示了控制器如何在宿主连接和断开连接时执行设置和清理工作,并在输入更改时请求更新
异步任务
“异步任务”的永久链接异步任务(如长时间运行的计算或网络 I/O)通常具有随时间变化的状态,并且需要在任务状态更改(完成、错误等)时通知宿主。
控制器是将任务执行和状态捆绑在一起以使其易于在组件中使用的绝佳方法。编写为控制器的任务通常具有宿主可以设置的输入和宿主可以渲染的输出。
@lit/task 包含一个通用的 Task 控制器,它可以从宿主拉取输入,执行任务函数,并根据任务状态渲染不同的模板。
您可以使用 Task 创建一个具有针对您的特定任务定制的 API 的自定义控制器。在这里,我们使用 NameController 包装 Task,它可以从演示 REST API 中获取指定名称列表中的一个。NameController 公开了一个 kind 属性作为输入,以及一个 render() 方法,该方法可以根据任务状态渲染四种模板之一。任务逻辑以及它如何更新宿主,都是从宿主组件中抽象出来的。
待办事项
- 动画