【问题标题】:Using web-components within Preact and typescript在 Preact 和 typescript 中使用 web 组件
【发布时间】:2025-12-22 22:15:07
【问题描述】:

我在Preact 中使用custom-elements 又名网络组件。问题是 Typescript 抱怨没有在 JSX.IntrinsicElements 中定义元素 - 在这种情况下是 check-box 元素:

<div className={styles.option}>
    <check-box checked={this.prefersDarkTheme} ref={this.svgOptions.darkTheme}/>
    <p>Dark theme</p>
</div>

错误信息(路径省略):

ERROR in MyComponent.tsx
[tsl] ERROR in MyComponent.tsx(50,29)
      TS2339: Property 'check-box' does not exist on type 'JSX.IntrinsicElements'.

我遇到了以下可能的解决方案,不幸的是没有工作:

  1. https://*.com/a/57449556/7664765 - 这是一个与问题无关的答案,但它涵盖了我的问题

我已尝试将以下内容添加到我的 typings.d.ts 文件中:

import * as Preact from 'preact';

declare global {
    namespace JSX {
        interface IntrinsicElements {
            'check-box': any; // The 'any' just for testing purposes
        }
    }
}

我的 IDE 将导入部分和 IntrinsicElements 显示为灰色,这意味着它没有被使用(?!)并且它无论如何都没有工作。我仍然收到相同的错误消息。

  1. https://*.com/a/55424778/7664765 - 同样对于反应,我尝试将其“转换”为 preact,我得到了与 1 相同的结果。

我什至在 squoosh 项目中找到了由 google 维护的 file,他们在其中执行以下操作来“填充”支持:

在与组件相同的文件夹中,包含以下内容的 missing-types.d.ts 文件,基本上与我的设置相同,但使用 index.ts 文件而不是 check-bock.ts,并且他们使用的是较旧的 TS 版本 v3.5.3

declare namespace JSX {
  interface IntrinsicElements {
    'range-input': HTMLAttributes;
  }
}

我假设他们的构建没有失败,那么它是如何工作的以及如何正确定义自定义元素以在 preact / react 组件中使用它们?

我目前正在使用typescript@v3.8.3preact@10.3.4

【问题讨论】:

    标签: typescript web-component custom-element preact


    【解决方案1】:

    这里是正确使用的属性,否则在传递key 时会报错。

    declare global {
      namespace JSX {
        interface IntrinsicElements {
          'xx-element1': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>; // Normal web component
          'xx-element2': React.DetailedHTMLProps<React.HTMLAttributes<HTMLInputElement>, HTMLInputElement>; // Web component extended from input
        }
      }
    }
    

    【讨论】:

      【解决方案2】:

      好的,我设法使用module augmentation 解决了它:

      declare module 'preact/src/jsx' {
          namespace JSXInternal {
      
              // We're extending the IntrinsicElements interface which holds a kv-list of
              // available html-tags.
              interface IntrinsicElements {
                  'check-box': unknown;
              }
          }
      }
      

      使用HTMLAttributes 接口,我们可以告诉 JSX 哪些属性可用于我们的自定义元素:

      // Your .ts file, e.g. index.ts
      declare module 'preact/src/jsx' {
          namespace JSXInternal {
              import HTMLAttributes = JSXInternal.HTMLAttributes;
      
              interface IntrinsicElements {
                  'check-box': HTMLAttributes<CheckBoxElement>;
              }
          }
      }
      
      // This interface describes our custom element, holding all its
      // available attributes. This should be placed within a .d.ts file.
      declare interface CheckBoxElement extends HTMLElement {
          checked: boolean;
      }
      

      【讨论】:

      • 这是未来的自己。
      • 哇,是的,我又来了。希望我能多次支持这个答案。
      【解决方案3】:

      使用 typescript 4.2.3 和 preact 10.5.13,以下是使用属性定义自定义标签名称的方法:

      declare module 'preact' {
          namespace JSX {
              interface IntrinsicElements {
                  'overlay-trigger': OverlayTriggerAttributes;
              }
          }
      }
      
      interface OverlayTriggerAttributes extends preact.JSX.HTMLAttributes<HTMLElement> {
          placement?: string;
      }
      

      区别:

      • 模块为'preact'(必须加引号)。
      • 命名空间是JSX
      • IntrinsicElements 值类型是扩展 HTMLAttributes 的接口。
      • 它通过名称 preact.JSX.HTMLAttributes 扩展 HTMLAttributes。
      • 它将基本元素类型提供为HTMLElement,以像事件侦听器一样填充道具/属性中的 eventTarget 类型。如果适用,您也可以输入SVGElement

      【讨论】:

      • 这适用于我使用 TypeScript 4.3.5 和 Preact 10.5.15,但需要注意的是,我还需要在声明之前import 'preact'
      【解决方案4】:

      有一种更好的方法可以做到这一点,而无需手动绑定事件。

      您可以使用 @lit-labs/reactcreateComponent 将 web 组件包装到 React 组件。

      import * as React from "react";
      
      import { createComponent } from "@lit-labs/react";
      import Slrange from "@shoelace-style/shoelace/dist/components/range/range.js";
      
      export const Range = createComponent(React, "sl-range", Slrange,{
          change: "sl-change" // map web component event to react event
      });
      
      
      import { Range } from "./SlRange";
      
      export default function App() {
        return (
          <div className="App">
            <Range max={6} min={2} step={2}></Range>
          </div>
        );
      }
      
      

      【讨论】:

      • 它仍然是一个 Web 组件,只是被抽象掉了