TypeScript 报 TS2351 错误是因为未注册自定义元素就直接使用,需在 JSX.IntrinsicElements 中声明并确保 customElements.define 已调用,否则运行时为 null;访问自定义属性须定义专属接口断言,不可仅用 as HTMLElement 或 as any。

自定义元素没注册就用,TypeScript 会报 TS2351 错误
浏览器允许你写 <my-button></my-button>,但 TypeScript 默认不认识这个标签——它不是内置元素(如 <div>),也不是 React 组件,编译器直接当语法错误处理。
常见错误现象:Cannot use JSX element type 'my-button'. Type '"my-button"' is not assignable to type 'JSX.IntrinsicElements'
- 必须在全局
JSX.IntrinsicElements接口中显式声明,例如:declare global { namespace JSX { interface IntrinsicElements { 'my-button': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>; 'data-grid': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>; } } } - 声明后仍需确保运行时已调用
customElements.define('my-button', ...),否则 DOM 操作时是null,和普通元素一样要判空 - 别只声明标签名却不指定属性类型——比如漏掉
disabled或data-id,后续访问el.disabled仍会触发 TS2339
用 as HTMLElement 断言会丢失自定义属性类型
获取自定义元素后写 document.querySelector('my-button') as HTMLElement,看似能过编译,但后续访问 el.myProp 或 el.setAttribute('loading', 'true') 依然报错。
根本原因:TypeScript 的 HTMLElement 接口不包含你的自定义属性或方法。
立即学习“前端免费学习笔记(深入)”;
- 正确做法是定义专属接口并断言:
interface MyButtonElement extends HTMLElement { loading: boolean; label: string; onClick(): void; } const btn = document.querySelector('my-button') as MyButtonElement; btn.loading = true; // ✅ 不报错 - 如果元素可能未升级(即刚插入 DOM 还没被 customElements 定义识别),要用
instanceof守卫:if (btn instanceof MyButtonElement) { ... } - 避免用
as any或!非空断言——它们绕过类型检查,等于放弃类型安全
TSX 中使用自定义元素要配 JSX.LibraryManagedAttributes
在 React 或类似框架的 TSX 文件里直接写 <my-button label="submit" />,即使已声明 IntrinsicElements,仍可能报属性缺失错误。
这是因为 TSX 默认只认标准 HTML 属性,而 label 是你自定义的 property,不是 attribute。
- 需要补充
LibraryManagedAttributes类型,告诉 TypeScript 如何映射 props 到元素实例:declare global { namespace JSX { interface LibraryManagedAttributes<C, P> { // 对自定义元素,把 props 直接映射为 element 实例属性 C extends CustomElementConstructor ? P : P; } } } - 更稳妥的做法是封装一层 React 组件包装自定义元素,用
React.forwardRef+useImperativeHandle暴露方法,由 TSX 类型系统统一管理 - 注意:若自定义元素用的是
attributeChangedCallback响应属性变更,那传入的 props 应转成字符串 attribute;若用的是 property setter,则需确保 TSX 编译器知道该 prop 是 runtime property 而非 HTML attribute
检查是否注册成功的运行时判断比类型声明更重要
类型声明只是让编译通过,真正出问题的是运行时——customElements.get('my-button') 返回 undefined 时,new MyButtonElement() 会失败,document.createElement('my-button') 创建的是普通 HTMLElement。
- 建议在初始化逻辑中加守卫:
if (!customElements.get('my-button')) { console.error('Custom element "my-button" not defined'); throw new Error('Missing web component registration'); } - 单元测试里不要只测 TS 编译,要实际调用
customElements.define后再做 DOM 查询和属性赋值 - 构建工具(如 Vite)若启用
define注入或动态 import,要注意注册时机早于元素首次渲染,否则 SSR 或首屏 JS 执行顺序错乱会导致类型和运行时不一致



















