🚣‍♀️

Props に HTML Standard の型定義を利用する

ComponentProps と HTMLProps の違いについて調べてみたので、その記録をば。

By jiyuujin at

#React
#TypeScript
Props に HTML Standard の型定義を利用するをはてなブックマークに追加

Props の型定義を知る

コンポーネントにおける Props の型定義で、既にある型定義を利用しましょう。

それはすなわち噛み砕いていうと、無理して自分用の型定義を作成する必要がないことを意味しています。

実際 Props の型定義では下記より選択できます。

  • IntrinsicElements
  • (より広義で) ComponentProps / ComponentPropsWithRef
  • HTMLAttributes
  • (より広義で) HTMLProps

結論をいうと、積極的に Props の型定義で ComponentProps を利用して欲しいと考えています。

IntrinsicElementsComponentProps で、複雑な型定義が書かれていますが、大まかにいえばラッパーのひとつと認識できます。

実際、開発者の目線で @types/react の内部実装を確認してみることをおすすめいたします。

IntrinsicElementsComponentProps

内部実装

// node_modules/@types/react/index.d.ts

interface ReactComponentElement<
  T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>,
  P = Pick<ComponentProps<T>, Exclude<keyof ComponentProps<T>, 'key' | 'ref'>>,
> extends ReactElement<P, Exclude<T, number>> {}

type ComponentProps<T extends keyof JSX.IntrinsicElements | JSXElementConstructor<any>> =
  T extends JSXElementConstructor<infer P>
    ? P
    : T extends keyof JSX.IntrinsicElements
    ? JSX.IntrinsicElements[T]
    : {}

HTMLAttributesHTMLProps

内部実装

// node_modules/@types/react/index.d.ts

interface HTMLProps<T> extends AllHTMLAttributes<T>, ClassAttributes<T> {
  // Standard HTML Attributes
}

interface AllHTMLAttributes<T> extends HTMLAttributes<T> {
  // Standard HTML Attributes
}

interface HTMLAttributes<T> extends AriaAttributes, DOMAttributes<T> {
  // Standard HTML Attributes
}

I/F 設計の例

Input / TextArea コンポーネントで HTML 標準の HTMLInputElement を利用してみます。

テキストフィールドコンポーネントを例に

React.ComponentPropsinput を指定しましょう。

export type InputProps = React.ComponentProps<'input'>

Input コンポーネントの props に React.ComponentProps で指定した型定義を利用しましょう。

import React from 'react'

export type _InputProps = React.ComponentProps<'input'>

export type InputProps = _InputProps

export function Input(props: InputProps) {
  const [input, setInput] = useState('')

  const onChange = (event: ChangeEvent<HTMLInputElement>) => setInput(event.currentTarget.value)

  const onClick = () => onSearch(input)

  return <input {...props} onChange={onChange} />
}

ここで _InputProps に存在しない型のひとつとして、今回は onSearch() を新たに Props の型定義へ追加したいと考えます。

import React from 'react'

export type _InputProps = React.ComponentProps<'input'>

export interface InputProps extends _InputProps {
  onSearch: (input: string) => void // onSearch() の型定義を追加します
}

export function Input(props: InputProps) {
  const { onSearch, ...rest } = props
  const [input, setInput] = useState('')

  const canSend = rest.value

  const onChange = (event: ChangeEvent<HTMLInputElement>) => setInput(event.currentTarget.value)

  const onClick = () => onSearch(input)

  return (
    <div>
      <input {...rest} onChange={onChange} />
      <button disabled={disabled || !canSend}>
        <Icon name={canSend ? 'carbon' : 'masked-carbon'} />
      </button>
    </div>
  )
}

複数行入力できるテキストフィールドコンポーネントを例に

React.ComponentPropstextarea を指定しましょう。

export type TextAreaProps = React.ComponentProps<'textarea'>

TextArea コンポーネントの props に React.ComponentProps で指定した型定義を利用しましょう。

import React from 'react'

export type _TextAreaProps = React.ComponentProps<'textarea'>

export type TextAreaProps = _TextAreaProps

export function TextArea(props: TextAreaProps) {
  const [input, setInput] = useState('')

  const onChange = (event: ChangeEvent<HTMLInputElement>) => setInput(event.currentTarget.value)

  const onClick = () => onSearch(input)

  return <input {...rest} onChange={onChange} />
}

ここで _TextAreaProps に存在しない型のひとつとして、今回は onSearch() を新たに Props の型定義へ追加したいと考えます。

import React, { ChangeEvent, useState } from 'react'

export type _TextAreaProps = React.HTMLProps<HTMLTextAreaElement>

export interface TextAreaProps extends _TextAreaProps {
  onSearch: (input: string) => void
}

export const TextArea = (props: TextAreaProps) => {
  const { rows = 1, onSearch, ...rest } = props
  const [input, setInput] = useState('')

  const onChange = (event: ChangeEvent<HTMLTextAreaElement>) => setInput(event.currentTarget.value)

  const onClick = () => onSearch(input)

  return (
    <div>
      <textarea {...rest} onChange={onChange} rows={rows} />
      <button disabled={disabled || !canSend}>
        <Icon name={canSend ? 'carbon' : 'masked-carbon'} />
      </button>
    </div>
  )
}
Props に HTML Standard の型定義を利用するをはてなブックマークに追加