如何分別在Angular、React、Preact、Vue和Svelte中使用Web元件? - netbasal

banq發表於2022-03-09

Web元件使我們能夠建立可重複使用的、可定製的元素。網路元件最大的優點是它的互操作性:由於瀏覽器原生支援,網路元件可以在任何HTML環境中使用,與任何框架一起使用,或者根本不使用框架。

網路元件的一個主要方面是封裝。你可以分離和隱藏標記結構、樣式和行為,這樣不同的頁面部分就不會發生衝突。

因此,網路元件是開發設計系統、可共享元件和嵌入式小工具的完美選擇。
讓我們來看看如何用行業內的頂級庫來使用本地Web元件。
 

使用 Lit 建立 Web 元件
使用純 Javascript 從頭開始​​構建 Web 元件很快就會變得一團糟,難以擴充套件和維護。

幸運的是,我們可以在Lit的幫助下構建快速、輕量級的 Web 元件。
Lit 的核心是一個消除樣板的元件基類,它提供反應狀態、作用域樣式和一個小巧、快速且富有表現力的宣告性模板系統。

讓我們構建一個最小的自定義select元素,看看我們如何處理原始和物件輸入值。
請注意,我們不會討論構建工具。你可以隨意使用任何你想要的東西。
 讓我們從select元素開始:

import { css, html, LitElement } from 'lit';

export class SelectElement<T extends Record<string, unknown>> extends LitElement {
  static styles = css`
    button[active] {
      background-color: honeydew
    }
  `
  @property({ type: Array }) 
  data: T[] = [];
  
  @property({ attribute: 'id-key' }) 
  idKey: keyof T = 'id' as keyof T;
  
  @property({ attribute: 'val-key' }) 
  valKey: keyof T = 'label' as keyof T;
  
  @property({ state: true }) 
  private activeItem: T | null = null;

  render() {    
    return html``
  }
}

customElements.define('ui-select', SelectElement);

我們建立一個SelectElement元件,並定義三個輸入:data、idKey和valKey。
我們還定義了一個activeItem狀態來跟蹤當前選定的專案。

繼續加入方法功能:

import { css, html, LitElement } from 'lit';
import { repeat } from 'lit/directives/repeat.js';

export class SelectElement extends LitElement {
  ...

  selectItem(item: T) {
    this.activeItem = item;

    const event = new CustomEvent<T>('select-item', {
      detail: item,
      bubbles: true,
      composed: true
    });

    this.dispatchEvent(event);
  }

  render() {    
    return html`
      <p>Active: ${this.activeItem ? this.activeItem[this.valKey] : 'None' }</p>
      
      ${repeat(
         this.data, 
         current => current[this.idKey], 
         current => html`
          <button ?active=${this.activeItem?.[this.idKey] === current[this.idKey]} 
                  @click=${() => this.selectItem(current)}>
            ${current[this.valKey]}
          </button>
      `)}
      `
  }
}

customElements.define('ui-select', SelectElement);


我們加入第三方庫中的repeat指令來有效地渲染我們的元素, 它接收三個引數
  1. 一個collection:
  2. 一個keyFunction(將單個專案作為引數並返回其唯一的鍵),
  3. 以及一個itemTemplate(將專案和其當前索引作為引數並返回TemplateResult)。

點選一個專案會將其設定為活動專案,並派發一個自定義的事件,父級可以監聽。

我們使用Lit的一個很好的功能,叫做布林屬性表示式。活動屬性將根據表示式的結果被新增或刪除。我們用它來設計活動元素。
如果你正在尋找一個lit檔案生成器,你可能會發現這個庫很有用。
 

在Angular中使用Web元件
首先,我們需要使用CUSTOM_ELEMENTS_SCHEMA模式。Angular會忽略它不認識的自定義元素(用破折號命名),而不是丟擲一個錯誤。

import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { TodosComponent } from './todos.component';

@NgModule({
  declarations: [TodosComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class TodosModule {}


現在我們可以在TodosComponent元件內使用我們的ui-select網路元件。

import '@org/ui/lib/select';
import { Component } from '@angular/core';
import { Todo, randTodo } from '@ngneat/falso';

@Component({
  selector: 'app-todos',
  template: `
    <button (click)="replace()">Replace</button>
    <ui-select [data]="todos" 
               (select-item)="onSelect($event)" 
               val-key="title">
    </ui-select>   
  `
})
export class TodosComponent {
  todos: Todo[] = randTodo({ length: 3 });

  replace() {
    this.todos = randTodo({ length: 3 });
  }

  onSelect(e: CustomEvent<Todo>) {
    console.log(e.detail);
  }
}


我們需要做的不多。Angular與自定義元素的整合是開箱即用的。
  • 它將todos元件屬性與ui-select元素的資料屬性繫結。
  • 事件繫結監聽select-item 事件,並在它被觸發時呼叫元件的onSelectItem()方法。

 

在Vue中使用Web元件
和Angular一樣,我們需要告訴Vue忽略自定義元素,這樣它在遇到自定義元素時就不會出現錯誤。下面是我們如何在Vite中做到這一點。

// vite.config.js
import vue from '@vitejs/plugin-vue'

export default {
  plugins: [
    vue({
      template: {
        compilerOptions: {
          // treat all tags with a dash as custom elements
          isCustomElement: (tag) => tag.includes('-')
        }
      }
    })
  ]
}

現在我們可以在我們的元件中使用我們的自定義元素。

<script setup lang="ts">
import { ref } from 'vue';
import '@org/ui/lib/select';
import { randTodo } from '@ngneat/falso';
const todos = ref(randTodo({ length: 3} ))
function replace() {
  todos.value = randTodo( {length: 3})
}
function onSelect(event: CustomEvent<Todo>) {
  console.log(event.detail)
}
</script>

<template>
  <button @click="replace">Replace</button>
  <ui-select :data="todos" @select-item="onSelect" val-key="title"></ui-select>
</template>


Vue讓使用自定義元素變得輕而易舉。
我們將該元素的資料屬性與todos ref繫結,並監聽select-item事件,當它被觸發時,執行onSelect函式。
  

在Svelte中使用Web元件
要在Svelte元件中使用自定義元素,我們不需要做任何特別的事情。匯入自定義元素並使用它。

<script lang="ts">
  import '@org/ui/lib/select';
  import { Todo, randTodo } from '@ngneat/falso';
  let todos: Array<Todo> = randTodo({ length: 3 });
  function replace() {
    todos = randTodo({ length: 3 });
  }
  function onSelect(e: CustomEvent<Todo>) {
    console.log(e.detail);
  }
</script>

<main>
  <button on:click={replace}>Replace</button>
  <ui-select data={todos} val-key="title" on:select-item={onSelect} />
</main>

 
 

在React中使用Web元件
React對自定義元素的支援是最不友好的。讓我們看看我們如何用React來使用它。

import '@org/ui/lib/select';
import { randTodo, Todo } from '@ngneat/falso';
import { SelectElement } from '@org/ui/lib/select';
import { useEffect, useRef, useState } from 'react';

export function Todos() {
  const [todos, setTodos] = useState<Todo[]>(randTodo({ length: 3 }));
  const ref = useRef<HTMLElement>();

  useEffect(() => {
    const callback = (e: CustomEvent<Todo>) => {
      console.log(e.detail);
    };

    ref.current!.addEventListener('select-item', callback);

    return () => ref.current!.removeEventListener('select-item', callback);
  }, [])

  return (
    <>
      <button onClick={() => setTodos(randTodo({ length: 3 }))}>Replace</button>

      <ui-select ref={ref} data={JSON.stringify(todos)} val-key="title"></ui-select>
    </>
  );
}

由於自定義元素不是React元件,它們被視為標準的HTML元素。
我們需要使用JSON.stringify,因為資料屬性值被當作一個字串處理。
由於我們將資料屬性轉換器設定為陣列,Lit將處理解析工作。
我們需要獲得自定義元素的引用,並手動註冊該事件,因為React不支援自定義元素事件。

為了讓它更乾淨,你可以使用諸如use-custom-element這樣的庫。

import '@org/ui/lib/select';
import { randTodo, Todo } from '@ngneat/falso';
import { SelectElement } from '@org/ui/lib/select';
import { useState } from 'react';
import useCustomElement from 'use-custom-element';

export function Todos() {
  const [todos, setTodos] = useState<Todo[]>(randTodo({ length: 3 }));

  const [customElementProps, ref] = useCustomElement({
    data: todos,
    'val-key': 'title',
    'select-item'(e: Todo) {
      console.log(e);
    }
  });

  return (
    <>
      <button onClick={() => setTodos(randTodo({ length: 3 }))}>Replace</button>

      <ui-select  {...customElementProps} ref={ref}></ui-select>
    </>
  );
}

 
 

在Preact中使用Web元件
Preact對Web元件的支援比React好得多。Preact的渲染器透過檢查DOM元素來決定是否使用一個屬性或屬性。
傳遞給DOM元素的未被識別的事件處理道具會完全按照指定的方式使用其套管進行註冊。

import '@org/ui/lib/select';
import { randTodo } from '@ngneat/falso';
import { useState } from 'preact/hooks';

const Todos = () => {
   const [todos, setTodos] = useState(randTodo({ length: 3 }));

   return (
    <>
      <button onClick={() => setTodos(randTodo({ length: 3 }))}>Replace</button>
      <ui-select data={todos} 
                 val-key="title" 
                 onselect-item={(e) => console.log(e.detail)}>
      </ui-select>
    </>
   )
}



 

相關文章