在Lit Web组件中使用RxJS
Lit是一个用于编写Web组件的库。Lit提供了反应式模板和实用工具,使可重用的Web组件变得简单。当管理一个具有复杂数据流的大型应用程序时,像RxJS这样的工具可以帮助管理数据。RxJS是一个可观察库,它可以帮助应用程序管理复杂的数据流。
在这篇博文中,我们将学习如何利用Lit和它的装饰器API,使RxJS Obervables的使用在我们的组件中易于管理。
注意:这篇高级概念文章假设了一些关于Lit和Rxjs的基本知识。
在这个例子中,我们将用Lit和RxJS创建一个小的计数器组件/小工具。然后,一旦工作,我们将重构我们的代码,利用一些特定的API来自动管理RxJS订阅。

Lit可以使用@property 或@state 装饰器更新/渲染属性更新。我们将使用@state 装饰器来跟踪我们的计数器值,以便在模板中呈现。
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { state } from 'lit/decorators/state.js';
import { interval, Subject } from 'rxjs';
import { scan, map } from 'rxjs/operators';
@customElement('my-element')
class MyElement extends LitElement {
@state() value = 0;
click$ = new Subject();
render() {
return html`
<button @click=${e => this.click$.next(e)} value="-1">-</button>
<p>value: ${this.value}</p>
<button @click=${e => this.click$.next(e)} value="1">+</button>
`;
}
constructor() {
super();
this.click$.pipe(
map(e => parseInt(e.target.value)),
scan((p, n) => p + n, 0)
).subscribe(value => this.value = value);
}
}
使用RxJSSubject ,我们可以从我们的按钮触发事件,以及订阅它们。在这个例子中,click ,我们通过next() ,向我们的主题派发一个新的事件。在我们的构造函数中,我们可以订阅同一个主题,并使用RxJS操作符,如map 和scan ,当每个事件进入我们的订阅时,我们的值就会累计起来。
虽然这个例子是有效的,但当组件被移除时,我们仍然需要从我们的Obervable/Subjects中取消订阅。
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { state } from 'lit/decorators/state.js';
import { interval, Subject, Subscription } from 'rxjs';
import { scan, map } from 'rxjs/operators';
@customElement('my-element')
class MyElement extends LitElement {
@state() value = 0;
subscription: Subscription;
click$ = new Subject();
render() {
return html`
<button @click=${e => this.click$.next(e)} value="-1">-</button>
<p>value: ${this.value}</p>
<button @click=${e => this.click$.next(e)} value="1">+</button>
`;
}
constructor() {
super();
// assign the subscription to unsubscribe later
this.subscription = this.click$.pipe(
map(e => parseInt(e.target.value)),
scan((p, n) => p + n, 0)
).subscribe(value => this.value = value);
}
disconnectedCallback() {
super.disconnectedCallback();
// when removed from the DOM, unsubscribe
this.subscription.unsubscribe();
}
}
在disconnectedCallback ,我们可以取消订阅我们的Observables,以确保我们的组件内没有内存泄漏。虽然这个演示很小,但它显示了在一个基于Lit的Web组件中使用RxJS的可能性。不过我们可以利用Lit Directive API来管理我们的订阅。
Lit Directives
Lit Directives提供了另一种与DOM和其他Web组件交互的方式。指令提供了各种生命周期的钩子来管理DOM的互动。
在这个例子中,我们将制作一个基本的observe 指令,它将自动管理我们的RxJS订阅。
<p>value: ${observe(this.count$)}</p>
Lit有几种不同的指令API类型,但对于我们的用例,我们将使用AsyncDirective 。
import { AsyncDirective } from 'lit/async-directive.js';
import { Directive, directive, EventPart, DirectiveParameters } from 'lit/directive.js';
import { Observable, Subject, Subscription } from 'rxjs';
class ObserveDirective extends AsyncDirective {
#subscription: Subscription;
render(observable: Observable<unknown>) {
this.#subscription = observable.subscribe(value => this.setValue(value));
return ``;
}
disconnected() {
this.#subscription?.unsubscribe();
}
}
export const observe = directive(ObserveDirective);
使用async指令,我们可以控制我们模板中的值的渲染输出。通过订阅我们传递给指令的Observable,我们可以在新事件发生时使用setValue 方法。setValue 方法将在每次调用时更新模板中的值。这使我们能够准确地控制我们的Observable的值应该何时更新。
这个指令API还提供了几个生命周期钩子,如disconnected 。当disconnected 钩子被调用时,我们可以取消对Observable的订阅,防止任何内存泄漏。
import { LitElement, html } from 'lit';
import { customElement } from 'lit/decorators/custom-element.js';
import { observe } from './rx-directive.js';
import { interval, Subject } from 'rxjs';
import { scan, map } from 'rxjs/operators';
@customElement('my-element')
class MyElement extends LitElement {
click$ = new Subject();
count$ = this.click$.pipe(
map(e => parseInt(e.target.value)),
scan((p, n) => p + n, 0)
);
render() {
return html`
<p>${Math.random()}</p>
<button @click=${e => this.click$.next(e)} value="-1">-</button>
<p>value: ${observe(this.count$)}</p>
<button @click=${e => this.click$.next(e)} value="1">+</button>
`;
}
}
现在,使用我们的observe 指令,我们减少了在Lit中使用Observable所需的代码量,而无需手动管理订阅。请看下面的完整演示,其中还包括一个用于发射事件的额外指令。