Интеграция с TypeScript

React Aria — это библиотека, предоставляющая доступные компоненты и хуки для работы с интерфейсами в React. Интеграция с TypeScript позволяет добиться строгой типизации, повышения предсказуемости кода и предотвращения распространённых ошибок на этапе компиляции. Важнейшими аспектами являются типизация хуков, пропсов компонентов и работа с DOM-элементами через ref.


Типизация хуков

Хуки React Aria обычно возвращают объект с набором атрибутов и обработчиков событий, которые следует применять к JSX-элементам. Для корректной работы в TypeScript необходимо указывать типы возвращаемых значений и целевых элементов.

Пример для useButton:

import {useButton} from '@react-aria/button';
import {ButtonHTMLAttributes, RefObject} from 'react';

interface CustomButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  label: string;
}

function CustomButton(props: CustomButtonProps) {
  const ref: RefObject<HTMLButtonElement> = React.useRef(null);
  const {buttonProps} = useButton(props, ref);

  return <button {...buttonProps} ref={ref}>{props.label}</button>;
}

Здесь:

  • RefObject<HTMLButtonElement> гарантирует корректный тип для DOM-элемента кнопки.
  • Интерфейс CustomButtonProps расширяет стандартные атрибуты кнопки HTML и добавляет необходимые пользовательские свойства.
  • useButton возвращает объект с buttonProps, который корректно типизирован TypeScript и может быть безопасно распространён на элемент.

Работа с useTextField и другими интерактивными компонентами

Для полей ввода и текстовых областей важно учитывать типизацию событий и атрибутов. React Aria использует универсальные типы для событий KeyboardEvent и FocusEvent.

import {useTextField} from '@react-aria/textfield';
import {InputHTMLAttributes, RefObject} from 'react';

interface CustomTextFieldProps extends InputHTMLAttributes<HTMLInputElement> {
  label: string;
}

function CustomTextField(props: CustomTextFieldProps) {
  const ref: RefObject<HTMLInputElement> = React.useRef(null);
  const {labelProps, inputProps} = useTextField(props, ref);

  return (
    <label {...labelProps}>
      {props.label}
      <input {...inputProps} ref={ref} />
    </label>
  );
}

Особенности:

  • labelProps и inputProps автоматически включают все необходимые атрибуты для доступности.
  • TypeScript обеспечивает проверку правильности типов при распространении атрибутов на элементы.

Комбинирование нескольких хуков

React Aria позволяет объединять несколько хуков для сложных компонентов, например, комбинирование фокуса, клавиатурной навигации и управления состоянием:

import {useButton} from '@react-aria/button';
import {useFocusRing} from '@react-aria/focus';
import {ButtonHTMLAttributes, RefObject} from 'react';

interface AccessibleButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
  label: string;
}

function AccessibleButton(props: AccessibleButtonProps) {
  const ref: RefObject<HTMLButtonElement> = React.useRef(null);
  const {buttonProps} = useButton(props, ref);
  const {isFocusVisible, focusProps} = useFocusRing();

  return (
    <button
      {...buttonProps}
      {...focusProps}
      ref={ref}
      style={{
        outline: isFocusVisible ? '2px solid blue' : 'none'
      }}
    >
      {props.label}
    </button>
  );
}

Преимущества:

  • TypeScript обеспечивает строгую проверку при объединении нескольких источников атрибутов (buttonProps, focusProps).
  • Уменьшается вероятность ошибок при деструктуризации и распространении свойств.

Типизация состояния компонентов

Некоторые хуки React Aria, например useCheckbox или useRadioGroup, требуют управления состоянием через объекты состояния, которые можно типизировать.

import {useCheckbox} from '@react-aria/checkbox';
import {useToggleState} from '@react-stately/toggle';
import {RefObject} from 'react';

interface CheckboxProps {
  label: string;
}

function CustomCheckbox({label}: CheckboxProps) {
  const state = useToggleState();
  const ref: RefObject<HTMLInputElement> = React.useRef(null);
  const {inputProps} = useCheckbox({isSelected: state.isSelected, onChange: state.setSelected}, state, ref);

  return (
    <label>
      <input {...inputProps} ref={ref} />
      {label}
    </label>
  );
}

Особенности:

  • useToggleState предоставляет типизированный объект состояния.
  • Свойства isSelected и onChange строго типизированы, что исключает неправильное присвоение значений.

Советы по строгой типизации

  1. Всегда указывать тип ref для элементов, передаваемых в хуки.
  2. Расширять стандартные HTML-атрибуты через интерфейсы TypeScript (ButtonHTMLAttributes, InputHTMLAttributes и т.д.).
  3. Использовать типы состояния из @react-stately, чтобы хуки получали корректно типизированные объекты.
  4. Комбинировать атрибуты через spread-оператор, сохраняя строгую проверку типов.
  5. Проверять совместимость с JSX-элементами, особенно при передаче props между компонентами.

Поддержка кастомных компонентов

React Aria позволяет строить полностью кастомные элементы с сохранением доступности. Важно типизировать все свойства, которые будут переданы в хук:

import {useMenu} from '@react-aria/menu';
import {AriaMenuProps} from '@react-types/menu';
import {RefObject} from 'react';

interface CustomMenuProps extends AriaMenuProps<HTMLElement> {
  items: string[];
}

function CustomMenu({items, ...props}: CustomMenuProps) {
  const ref: RefObject<HTMLUListElement> = React.useRef(null);
  const {menuProps} = useMenu(props, ref);

  return (
    <ul {...menuProps} ref={ref}>
      {items.map(item => <li key={item}>{item}</li>)}
    </ul>
  );
}

Типизация гарантирует:

  • Корректность использования интерфейса AriaMenuProps.
  • Безопасную передачу ref на HTML-элемент.
  • Проверку атрибутов доступности на этапе компиляции.

Итог

Интеграция React Aria с TypeScript требует внимательного подхода к типизации ref, пропсов компонентов и состояния. Строгая типизация не только предотвращает ошибки, но и обеспечивает автоматическую документацию кода, улучшает автодополнение в IDE и повышает качество разработки доступных интерфейсов.