🧩

デフォルトpropsを継承する型 ComponentPropsWithoutRef

2022/02/06に公開

NextJs(React)×TypeScriptで開発を行う場合の、コンポーネント作成のベストプラクティスについて調べて見ました。

すると、自分がやっていたコンポーネント型指定がナンセンスであることが発覚、今回コンポーネント大改善に成功しましたので共有します!

改善前

今までやってしまっていたコンポーネント型定義。

import React from 'react'
import styles from './Button.module.scss'

interface Props {
  color?: 'primary' | 'secondary'
  disabled?: boolean
  onClick?: () => void
  style?: React.CSSProperties
  children?: React.ReactNode
}

export const Button = (props: Props) => {
  const { color, disabled, onClick, style, children } = props

  return (
    <button
      type='button'
      className={[
        styles.root,
        `${color ? styles[color] : styles.primary}`,
        `${disabled ? styles.disabled : ''}`
      ].join(' ')}
      style={style}
      onClick={onClick}
    >
      {children}
    </button>
  )
}

ページを構成する場合、他のコンポーネントとの間隔の調整などが必要になる。

上記コードの問題点は、コンポーネントはページやレイアウトで利用する場合、基本的にmarginを持たないコンポーネントに、コンポーネント間の余白を持たせるためには別途スタイルを当てる必要があるので、明示的にstyle propsを用意していた。

これでは、全てのコンポーネントに必要だと想定される、style propsをわざわざ設定することになり、非常に冗長である。

改善後

import React from 'react'
import styles from './Button.module.scss'

interface Props extends React.ComponentPropsWithoutRef<'button'> {
  color?: 'primary' | 'secondary'
  disabled?: boolean
  onClick?: () => void
  children?: React.ReactNode
}

export const Button = ({ color, disabled, onClick, style, children, ...props }: Props) => {
  return (
    <button
      type='button'
      className={[
        styles.root,
        `${color ? styles[color] : styles.primary}`,
        `${disabled ? styles.disabled : ''}`
      ].join(' ')}
      onClick={onClick}
      {...props}
    >
      {children}
    </button>
  )
}

改善後は、style propsを取り除き、Props型に、ComponentPropsWithoutRefを継承することで、<button>のデフォルトpropsを全て継承することができます。

参考記事

Discussion