📌

Nuxt3における単一責任の原則(SRP)について【インターン備忘録 - 2025年2月編】

2025/03/10に公開

最近になり、フロントエンドにおける設計思想について、少しかじったので、自分なりに整理してみました。

Nuxt3における単一責任の原則

2月中旬あたりから、割と大きなリファクタリングを兼ねたコンポーネントの整理と新機能の実装を進めています。Nuxt3にも慣れてきたものの、「なんとなく」で開発している部分も多かったため、なんとなくを払拭するために、勉強しています。

いきなりフロントエンドにおける設計思想を読み出したわけではなく、最初はNuxt3におけるディレクトリ設計のベストプラクティス的なのは、どれなのかについて調べていました。(ディレクトリ設計については、今回は割愛)そこから、段々とフロントエンドの設計思想に関する記事や本を読み出し、今回のテーマである「Nuxt3における責務分割・単一責任の原則」に至ります。

とまあ、成り行き(きっかけ)はさておき...

そもそも「単一責任の原則」とはなんぞや?

「単一責任の原則(Single Responsibility Principle, SRP)」とは、

プログラミングに関する原則であり、モジュール、クラスまたは関数は、単一の機能について責任を持ち、その機能をカプセル化するべきであるという原則である。

(Wikipediaより参照[1])

簡単に言えば、「1つのクラスは1つの責任(役割)だけを持つべき」という考え方のこと。
1つのクラスが複数の責任(役割)を持つことを避け、1つの明確な目的を持たせるという意味。

ソフトウェアの規模が大きくなるにつれ、コード自体も段々と肥大化していきます。
その中で、1つのクラスが複数の役割を持ってしまうと、ある機能の変更が他の機能に予期せぬ影響を与えてしまい、結果的に不具合が発生してしまいます。また、コードの可読性・メンテナンス性が下がってしまい、開発効率という観点で、大きな影響を与えてしまいます。

そうならないようにと編み出された考え方が「単一責任の原則(SRP)」になります。この考え方の大元?は、「SOLID原則」が元になっているので、詳しく知りたい方はそちらをご覧ください。(ここでは、単一責任の原則にフォーカスを絞っていきます)

Nuxt3との親和性

Nuxt3は、既に単一責任の原則を強く意識されています。OptionsからComposition APIに代わり、機能ごとの明確な分離が容易になり、UIコンポーネント、ページコンポーネント、コンポーザブルのように、階層的な責務分割が明確に行うことができます。

1. UIコンポーネント

純粋な見た目の表示にのみ責任を持つコンポーネント群です。データの取得やステート管理などのロジックは持たず、propsやdefineModel()経由でデータを受け取り、表示することに専念します。

components/UI/Button.vue
<template>
  <button 
    :class="buttonClass" 
    @click="$emit('click')"
  >
    <slot />
  </button>
</template>

2. コンポーザブル

ビジネスロジック・再利用可能なロジックを分離・管理します。基本的には、データの取得、加工・整形、ステート管理などを行います。

composables/useUser.ts
export const useUser = () => {
  const user = ref(null)
  const fetchUser = async (id: string) => {
    user.value = await useFetch(`/api/users/${id}`)
  }
  
  return {
    user,
    fetchUser
  }
}

3. ページコンポーネント

UIコンポーネントとコンポーザブルを組み合わせて、特定のページの表示を行います。「各機能の橋渡し役」みたいなものですね。

pages/users/[id].vue
<template>
  <div>
    <UserProfile :user="user" />
    <UserActions @update="handleUpdate" />
  </div>
</template>

<script setup>
const { user, fetchUser } = useUser()
const route = useRoute()

onMounted(() => {
  fetchUser(route.params.id)
})
</script>

どこまで分割するか(コンポーネントの粒度)

多くの人は、ここで悩みますよね、、、
大きすぎても小さすぎても問題が生じかねないので、バランスが重要。

私は、
・コード全体の量
・機能の独立性
・状態の共有が親子関係で結びついてしまっている
の3つのポイントで決めるようにしました。

最後の「状態の共有が親子関係で結びついてしまっている」というのは、
 ・状態の更新が頻繁で、多くのprops/emitのやり取りがある
 ・親子間で同期を取らないといけないデータが多い(整合性の担保が必要)
 ・子の操作が直接親の状態に影響する
のことを指します。例として挙げるのであれば、複数のステップがあるフォームなどですかね。

無理やり分割してしまうと、逆にデバッグが大変になったり、可読性が低下してしまったりしてしまうので、要注意。難しい部分ではあるものの、開発者によって基準が曖昧なので、臨機応変にっていうのが多いっぽい...?

終わりに

単一責任の原則は、Nuxt3だけではなくソフトウェア開発全般に応用することができるので、是非とも知っておきたい分野だなと思っているものの、触れる機会がないと通り過ぎてしまうので、気をつけたいところ

気になった方は、SOLIDの原則と検索すると、メチャクチャ有用な記事がいっぱい出てくるので、そちらの記事を参照することをオススメする。

3月も頑張ろう!

脚注
  1. https://en.wikipedia.org/wiki/Single-responsibility_principle ↩︎

リバナレテックブログ

Discussion