🧩
デフォルトpropsを継承する型 ComponentPropsWithoutRef
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