next/link は atoms に含めるべきか?

2 min read読了の目安(約1800字
  • ① a タグとして機能する Component
  • ② Link コンポーネントとして機能する Component

全く同じ見た目のこのふたつの Component がある場合、どうしますか?全く同じスタイルで ①② を両方作る方もいるかもしれませんが、私は ① しか作りません。理由は単純で、Link の子 Component として ① を使えば良いからです。

なぜ ② を作ってしまう?

理由は様々だと思いますが、おそらくこの様な理由ではないでしょうか。

  • 都度 Link を import するのが面倒
  • 型関連の都合で必要だから
  • Link と分離するとエラーになるから(え?)

もし三番目が理由だとしたら、ここを読みましょう。
React.forwardRefを使え、ルーク(意訳)」

https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-function-component

React.forwardRef ...?

ひと昔前は、React のスタイリングと言えば、CSS in JS がマジョリティでした。しかし今は、Tailwind や CSS Modules も選択肢として挙がり、拮抗している状況です。

これまで CSS in JS しか使ったことがない場合React.forwardRefは初耳かもしれません。CSS in JS は暗黙的に ref forwarding を施しており、透過的に ref が挿ささります。どういうことかというと、このようなボタン定義があった場合。

const Button = styled.a`
  color: #00f;
`;

Tailwind や CSS Modules で等価実装をしたい場合、次のものが必要です。CSS in JS と比べると一手間かかりますね。

const Button = React.forwardRef(({ className, ...props }, ref) => {
  return <a {...props} className={clsx(className, styles.btn)} ref={ref} />;
});
.btn {
  color: #00f;
}

CSS in JS の便利さに慣れてしまっていたら、なかなか気付きにくい点ではないでしょうか?ref forwarding が施されていない Function Component を Link の子にしてしまうと、この様なエラーがでます。

const Button: React.FC = ({ className, ...props }) => {
  return <a {...props} className={clsx(className, styles.btn)} />;
});
Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

atoms は ref forwarding しておくが吉

今回は next/link にフォーカスしましたが、input タグでも同様の議題はあります。react-hook-form などのフォームライブラリにおいては、ref forwarding が施されたコンポーネントの方が扱いやすく、ref forwarding が施されていないことが導入障壁になることがあります。

CSS in JS で styled.input などを使われていたならば、これも同様に気づかない観点だったのではないでしょうか?CSS in JS の功罪は、暗黙的に ref forwarding していたことだと思います。React.forwardRef を知る機会は、CSS in JS を手放した時に訪れます。