LitWebComponents开发技能Skill lit

Lit Web Components开发技能是专门用于构建现代化、高性能Web组件的专业能力。该技能涵盖Lit框架的核心功能,包括自定义元素创建、响应式属性管理、Shadow DOM样式封装、组件生命周期控制等关键技术。适用于开发跨框架兼容的组件库、设计系统、微前端架构和企业级应用组件。关键词:Web Components开发、Lit框架、自定义元素、Shadow DOM、响应式组件、前端组件库、微前端架构、跨框架兼容、组件封装、前端开发。

前端开发 0 次安装 0 次浏览 更新于 2/26/2026

name: lit description: 使用Lit进行Web Components开发,包括自定义元素、响应式属性、Shadow DOM和互操作性。 allowed-tools: Read, Write, Edit, Bash, Glob, Grep

Lit技能

使用Lit构建Web Components的专家级协助。

能力

  • 创建具有响应式属性的Lit元素
  • 实现Shadow DOM样式
  • 处理事件和生命周期回调
  • 构建可组合的组件库
  • 确保框架互操作性
  • 配置Lit的SSR(服务器端渲染)

使用场景

在以下情况时调用此技能:

  • 构建框架无关的组件
  • 创建设计系统基础组件
  • 实现自定义元素
  • 确保跨框架兼容性
  • 构建微前端

输入参数

参数 类型 是否必需 描述
elementName 字符串 自定义元素名称(必须包含连字符)
properties 数组 响应式属性
shadowDom 布尔值 使用Shadow DOM(默认:true)
typescript 布尔值 使用TypeScript(默认:true)

组件模式

基础Lit元素

// src/components/user-card.ts
import { LitElement, html, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';

interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
}

@customElement('user-card')
export class UserCard extends LitElement {
  static styles = css`
    :host {
      display: block;
      padding: 1rem;
      border: 1px solid #e5e7eb;
      border-radius: 0.5rem;
    }

    .user-card {
      display: flex;
      gap: 1rem;
    }

    .avatar {
      width: 64px;
      height: 64px;
      border-radius: 50%;
      overflow: hidden;
      background: #e5e7eb;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .avatar img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    .initials {
      font-weight: bold;
      font-size: 1.25rem;
    }

    button {
      padding: 0.5rem 1rem;
      border: none;
      border-radius: 0.25rem;
      cursor: pointer;
    }

    button.primary {
      background: #3b82f6;
      color: white;
    }
  `;

  @property({ type: Object })
  user!: User;

  @property({ type: Boolean })
  editable = false;

  @state()
  private isEditing = false;

  @state()
  private editedName = '';

  private get initials() {
    return this.user.name
      .split(' ')
      .map(n => n[0])
      .join('')
      .toUpperCase();
  }

  private startEditing() {
    this.editedName = this.user.name;
    this.isEditing = true;
  }

  private save() {
    this.dispatchEvent(new CustomEvent('user-updated', {
      detail: { ...this.user, name: this.editedName },
      bubbles: true,
      composed: true,
    }));
    this.isEditing = false;
  }

  private cancel() {
    this.isEditing = false;
  }

  render() {
    return html`
      <div class="user-card">
        <div class="avatar">
          ${this.user.avatar
            ? html`<img src=${this.user.avatar} alt=${this.user.name} />`
            : html`<span class="initials">${this.initials}</span>`}
        </div>

        <div class="info">
          ${this.isEditing
            ? html`
                <input
                  .value=${this.editedName}
                  @input=${(e: Event) => this.editedName = (e.target as HTMLInputElement).value}
                  @keydown=${(e: KeyboardEvent) => e.key === 'Enter' && this.save()}
                />
                <button class="primary" @click=${this.save}>保存</button>
                <button @click=${this.cancel}>取消</button>
              `
            : html`
                <h2>${this.user.name}</h2>
                <p>${this.user.email}</p>
                ${this.editable
                  ? html`<button @click=${this.startEditing}>编辑</button>`
                  : null}
              `}
        </div>
      </div>
    `;
  }
}

declare global {
  interface HTMLElementTagNameMap {
    'user-card': UserCard;
  }
}

响应式控制器

// src/controllers/fetch-controller.ts
import { ReactiveController, ReactiveControllerHost } from 'lit';

export class FetchController<T> implements ReactiveController {
  host: ReactiveControllerHost;

  data: T | null = null;
  loading = false;
  error: Error | null = null;

  private abortController: AbortController | null = null;

  constructor(host: ReactiveControllerHost, private url: string) {
    (this.host = host).addController(this);
  }

  hostConnected() {
    this.fetch();
  }

  hostDisconnected() {
    this.abortController?.abort();
  }

  async fetch() {
    this.abortController?.abort();
    this.abortController = new AbortController();

    this.loading = true;
    this.error = null;
    this.host.requestUpdate();

    try {
      const response = await fetch(this.url, {
        signal: this.abortController.signal,
      });

      if (!response.ok) {
        throw new Error(`HTTP错误: ${response.status}`);
      }

      this.data = await response.json();
    } catch (e) {
      if ((e as Error).name !== 'AbortError') {
        this.error = e as Error;
      }
    } finally {
      this.loading = false;
      this.host.requestUpdate();
    }
  }
}

// 在组件中使用
@customElement('user-list')
export class UserList extends LitElement {
  private usersController = new FetchController<User[]>(this, '/api/users');

  render() {
    if (this.usersController.loading) {
      return html`<p>加载中...</p>`;
    }

    if (this.usersController.error) {
      return html`<p>错误: ${this.usersController.error.message}</p>`;
    }

    return html`
      <ul>
        ${this.usersController.data?.map(
          user => html`<li>${user.name}</li>`
        )}
      </ul>
    `;
  }
}

自定义渲染指令

// src/directives/highlight.ts
import { Directive, directive, PartInfo } from 'lit/directive.js';

class HighlightDirective extends Directive {
  render(text: string, query: string) {
    if (!query) return text;

    const regex = new RegExp(`(${query})`, 'gi');
    const parts = text.split(regex);

    return parts.map(part =>
      regex.test(part)
        ? document.createElement('mark').textContent = part
        : part
    );
  }
}

export const highlight = directive(HighlightDirective);

// 使用
html`<p>${highlight(text, searchQuery)}</p>`

插槽和组合

@customElement('modal-dialog')
export class ModalDialog extends LitElement {
  static styles = css`
    :host {
      display: none;
    }

    :host([open]) {
      display: block;
    }

    .backdrop {
      position: fixed;
      inset: 0;
      background: rgba(0, 0, 0, 0.5);
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .dialog {
      background: white;
      padding: 1.5rem;
      border-radius: 0.5rem;
      max-width: 500px;
      width: 100%;
    }
  `;

  @property({ type: Boolean, reflect: true })
  open = false;

  private handleBackdropClick(e: Event) {
    if (e.target === e.currentTarget) {
      this.dispatchEvent(new CustomEvent('close'));
    }
  }

  render() {
    return html`
      <div class="backdrop" @click=${this.handleBackdropClick}>
        <div class="dialog" role="dialog" aria-modal="true">
          <header>
            <slot name="header"></slot>
          </header>
          <main>
            <slot></slot>
          </main>
          <footer>
            <slot name="footer"></slot>
          </footer>
        </div>
      </div>
    `;
  }
}

// 使用
html`
  <modal-dialog ?open=${showModal} @close=${() => showModal = false}>
    <h2 slot="header">确认操作</h2>
    <p>确定要继续吗?</p>
    <div slot="footer">
      <button @click=${confirm}>确认</button>
      <button @click=${cancel}>取消</button>
    </div>
  </modal-dialog>
`

最佳实践

  • 使用自定义元素命名约定(连字符分隔)
  • 利用Shadow DOM实现封装
  • 使用@state管理内部状态
  • 分发组合事件进行父组件通信
  • 实现响应式控制器用于可重用逻辑

目标流程

  • web-component-library
  • design-system-creation
  • micro-frontend-architecture
  • framework-agnostic-components