Как добавить атрибуты к существующим элементам HTML в TypeScript/JSX?

Кто-нибудь знает, как правильно добавить/расширить все собственные атрибуты HTML-элементов с помощью пользовательских?

С документацией TypeScript для объединения интерфейсов я подумал, что я мог бы просто сделать это:

interface HTMLElement {
    block?: BEM.Block;
    element?: BEM.Element;
    modifiers?: BEM.Modifiers;
}

<div block="foo" />; // error

Но я получаю следующую ошибку Intellisense в vscode 1.6.1 (последняя версия):

[ts] Свойство 'block' не существует для типа 'HTMLProps'.

HTMLProps на который они ссылаются, это React.HTMLProps<T> и элемент div объявлен для его использования следующим образом:

namespace JSX {
    interface IntrinsicElements {
        div: React.HTMLProps<HTMLDivElement>
    }
}

Я попытался переопределить div, но безрезультатно.

Связанный: https://github.com/Microsoft/TypeScript/issues/11684

Редактировать: Вот, что в конечном итоге работает для меня:

declare module 'react' {
    interface HTMLAttributes<T> extends DOMAttributes<T> {
        block?: string
        element?: string
        modifiers?: Modifiers // <-- custom interface
    }
}

Ответ 1

Похоже, что в более старых версиях файлов определений типов (v0.14) интерфейсы были просто объявлены в глобальном пространстве имен React, поэтому ранее вы могли использовать стандартный синтаксис слияния.

declare namespace React {

    interface HTMLProps<T> extends HTMLAttributes, ClassAttributes<T> {
    }
}

Однако новая версия файла d.ts(v15.0) объявила все внутри модуля. Поскольку модули не поддерживают слияние, насколько мне известно, сейчас единственным вариантом является module augmentation: https://www.typescriptlang.org/docs/handbook/declaration-merging.html.

Я сделал следующий эксперимент, и он работал для меня:

import * as React from 'react';

declare module 'react' {
     interface HTMLProps<T> {
        block?:string;
        element?:string;
        modifiers?:string;
    }

}

export const Foo = () => {

    return (
        <div block="123" element="456">
        </div>
    )
};

Очевидно, это довольно утомительно, вы можете поместить код дополнения в другой файл, как показано в примере из справочника по машинописи, и импортировать его:

import * as React from 'react';
import './react_augmented';

Но это все еще довольно грязно. Поэтому, возможно, лучше всего решить проблему с участниками файла определения типа.

Ответ 2

Я хотел использовать гламур createElement, который добавляет css prop к каждому элементу.

Чтобы добавить к принятому ответу, увеличение модуля, похоже, делает трюк, но HTMLProps работает только для элементов без ввода. Похоже, что правильные интерфейсы для расширения HTMLAttributes и SVGAttributes.

declare module 'react' {
  interface HTMLAttributes<T> {
    css?: any
  }

  interface SVGAttributes<T> {
    css?: any
  }
}

Чтобы избежать импорта расширения модуля в каждом компоненте, повторно экспортируйте элемент createElement:

// createElement.ts
import { createElement } from 'glamor/react'

declare module 'react' {
  interface HTMLAttributes<T> {
    css?: any
  }

  interface SVGAttributes<T> {
    css?: any
  }
}

export default createElement

Затем скажите TS, чтобы использовать наш createElement для JSX с этим tsconfig:

{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "createElement"
  }
}

Использование:

// MyComponent.tsx
import createElement from './createElement'

export default function MyComponent() {
  return <div css={{ color: 'red' }} />
}

Ответ 3

Актуальный пример (май 2019)

Файл определения типа index.d.ts (по умолчанию - index.d.ts при index.d.ts create-react-app) содержит список всех стандартных элементов HTML, а также известные атрибуты.

Для того чтобы разрешить пользовательские атрибуты HTML, вам нужно определить его типизацию. Сделайте это, расширив интерфейс HTMLAttributes:

declare module 'react' {
  interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
    // extends React HTMLAttributes
    custom?: string;
  }
}