在Lit Web组件中使用RxJS的详细教程(附代码)

413 阅读3分钟

在Lit Web组件中使用RxJS

Lit是一个用于编写Web组件的库。Lit提供了反应式模板和实用工具,使可重用的Web组件变得简单。当管理一个具有复杂数据流的大型应用程序时,像RxJS这样的工具可以帮助管理数据。RxJS是一个可观察库,它可以帮助应用程序管理复杂的数据流。

在这篇博文中,我们将学习如何利用Lit和它的装饰器API,使RxJS Obervables的使用在我们的组件中易于管理。

注意:这篇高级概念文章假设了一些关于Lit和Rxjs的基本知识。

在这个例子中,我们将用Lit和RxJS创建一个小的计数器组件/小工具。然后,一旦工作,我们将重构我们的代码,利用一些特定的API来自动管理RxJS订阅。

Example counter widget built with RxJS and a Lit based Web Component

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操作符,如mapscan ,当每个事件进入我们的订阅时,我们的值就会累计起来。

虽然这个例子是有效的,但当组件被移除时,我们仍然需要从我们的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所需的代码量,而无需手动管理订阅。请看下面的完整演示,其中还包括一个用于发射事件的额外指令。