偶発的な出来事によりフロントエンドを始めて1年ちょっとが経ちましたが、全然わからないまま日々実装している毎日です。
ひょんなことから「Container/Presentationalパターン」を知ったのでそれについてNext.jsのApp Routerとの親和性が高そうだと感じたのでちょっとだけ考えてみます。※ほぼ備忘録です。
まあ、私が詳しく書かなくとも以下の記事ですごく丁寧に説明されていますのでこれだけでOKな方はぜひ参照してください。
またClient ComponentやServer Componentなどの概念の説明は省略しているのでご了承ください……。
Container/PresentationalパターンはApp Routerとの親和性が高い
言いたいことはほぼこれだけです。
App Routerでは Client と Server でコンポーネントを分けることができます。ファイル名の先頭とかに use client
や use sever
をつけることで、クライアント側のコンポーネントなのかサーバー側のコンポーネントなのかを位置づけることができます。
特になにもしないと普通に以下のエラーを吐くので、Next.jsのApp Routerを使う人は誰しもが気付かされるでしょう。
ReactServerComponentsError:
You're importing a component that needs useState.
It only works in a Client Component but none of its parents are marked with "use client",
so they're Server Components by default.
さらに適当に use server
を使おうとして以下のエラーが出て「なんやねん」となるはずです。
It is not allowed to define inline "use server" annotated Server Actions in Client Components.
これら2つのエラーにより、開発者はわりと厳密に use client
と use server
の使い分けのルールを守る必要が出てきます。
で、このルールがそのまま「Container/Presentationalパターン」に適用できるわけです。
Container
Containerはデータ取得しそれをPresentationへ渡す役割です。つまりApp Routerでいうところの Server Component と似てます。
より厳密にClientと関心事を分離するために import 'server-only'
を使ってもいいですね。
参考:
Presentational
こちらは従来どおりのReactコンポーネントです。App Routerでは Client Component という名前になってます。
見た目上の振る舞いや状態管理(useEffect、useHook、useState)を管理するコンポーネント群です。
アーキテクチャ設計
個人的にこんな感じにすると良いかもと思っているアーキテクチャのイメージ図としては以下のとおりです。ディレクトリ構成ともうまくマッチさせることができそう。
- Client Component: use client
- Server Component: use server
- Server Action: import 'server-only'
Server Componentという概念が生まれてくれたおかげで、ビジネスロジックを Client Componentからなるべく排除することができます。
個人的にStorybook駆動でのUIコンポーネント開発に懐疑的だったのは、状態管理という名目で多くのビジネスロジックを肩代わりしていたUIコンポーネントに対して、必要十分なテストを書くことのメリットをあまり感じていなかったからです。
しかし、Server ComponentのおかげでビジネスロジックをUIから切り離すことが割と簡単にできるようになった(ついでにブラウザ側からそれらを秘匿できるようになった)こともあり、App Routerによる開発であればStorybook駆動開発ももう少し楽になるんじゃないかと期待しています。
Server Actionについては言わずもがなです。APIを叩いてデータ変換する部分なので、DTOとしての役割も担えそうです。Next.jsでDDDも従来よりやりやすい気がします。
全体的に責任(関心事)の所在がディレクトリ単位でも分割できるので、テストも書きやすそう。
まとめ
Next.jsのApp Routerのアーキテクチャやディレクトリ設計に悩んでいた方、是非Container/Presentationalパターン使ってみてください。