第13回:Composable はどこまで分割すべきか

― 分けすぎても、まとめすぎても壊れる理由 ―

※ChatGPTを使用して記事を作成しています。

Compose を書いていると、必ず迷う瞬間があります。

  • この UI は関数を分けるべき?
  • 1画面1Composableで十分?
  • 小さく分割するのが正義?
  • パフォーマンス的に不利では?

そして気づくと、

  • 1,000行の巨大Composable
  • 50個に分割された謎の部品群

のどちらかに転びます。

この記事では、

  • なぜ分割が必要なのか
  • どこまで分けるのが妥当か
  • 分けすぎると何が起きるか

を整理します。

まず結論

Composable は「見た目」ではなく
責務単位で分割する

これが原則です。

失敗例①:巨大Composable症候群

@Composable
fun ProfileScreen(state: UiState) {
    Column {
        TopBar(...)
        if (state.isLoading) {
            Loading()
        } else {
            UserHeader(state.user)
            StatsSection(state.stats)
            PostList(state.posts)
            Footer(...)
        }
        if (state.showDialog) {
            ConfirmDialog(...)
        }
    }
}

最初は問題ありません。

しかし機能追加が続くと、

  • 条件分岐が増える
  • 表示ロジックが増える
  • Stateの参照箇所が散らばる

やがて、修正が怖いコードになります。

なぜ巨大Composableは危険なのか

① 再利用できない

UIの一部だけを別画面で使えない。

② 責務が混在する

  • ヘッダー表示
  • リスト表示
  • ダイアログ制御

が1つの関数に存在する。

③ テスト不能

Previewもしづらい。

失敗例②:分割しすぎ問題

逆にこうなるケースもあります。

@Composable
fun TitleText(text: String)

@Composable
fun DescriptionText(text: String)

@Composable
fun DividerLine()

@Composable
fun IconWrapper(icon: ImageVector)

一見きれいですが、

  • 意味のない分割
  • 文脈が見えない
  • 呼び出し側が複雑

になります。

これは「見た目単位」で分割してしまった例です。

分割の正しい基準

基準①:責務が独立しているか?

例えば、

  • ヘッダー部分
  • 投稿一覧部分
  • ダイアログ部分

これらは機能的に独立しています。

@Composable
fun ProfileHeader(user: UserUiModel)

@Composable
fun PostSection(posts: List<PostUiModel>)

@Composable
fun DeleteDialog(...)

この分割は意味があります。

基準②:Stateの関心が違うか?

Stateの粒度とComposableの粒度は連動します。

例:

  • formState は FormSection に渡す
  • listState は ListSection に渡す

巨大な UiState を丸ごと渡すのではなく、

FormSection(state.formState)
ListSection(state.listState)

のように分けます。

基準③:単独で Preview できるか?

良いComposableは、

  • 引数だけで成立する
  • 外部依存がない
  • ViewModelを知らない

という特徴があります。

再コンポーズとの誤解

よくある不安:

分割すると再コンポーズが増えませんか?

結論:

分割したほうが再評価範囲はむしろ小さくなることが多い

Composeは差分計算します。

小さく責務を分けたほうが、

  • 変更箇所が局所化
  • スキップ判定が効きやすい

です。

「Stateを持つComposable」はどこまで許すか

原則はシンプルです。

① 画面全体のState → ViewModel

② 局所的なUI状態 → remember

例:

@Composable
fun ExpandableCard(content: String) {
    var expanded by remember { mutableStateOf(false) }
}

これは問題ありません。

しかし、画面全体に影響する状態を内部に閉じ込めると破綻します。

実務での分割テンプレート

  1. Screen(ViewModel接続)
  2. Section(責務単位)
  3. Component(UI部品)

例:

ProfileScreen
 ├── ProfileHeader
 ├── StatsSection
 ├── PostSection
 │     ├── PostItem
 │     └── ...
 └── ConfirmDialog

この階層は自然で読みやすい構造です。

分割のしすぎを防ぐ質問

  • それは意味単位か?
  • 別の画面で再利用する可能性があるか?
  • Stateの関心は独立しているか?
  • テストやPreviewが楽になるか?

YESが少ないなら分割不要です。

まとめ

Composable 分割の本質は、

UIを小さくすることではない
責務を明確にすること

です。

  • 巨大Composable → 責務混在で破綻
  • 過剰分割 → 文脈崩壊で可読性低下

ちょうどよい分割は、

  • 状態の粒度と一致し
  • 関心ごとが分離され
  • 単体で成立する

Composable です。

タイトルとURLをコピーしました