第17回:Composableの引数が増えすぎる問題

社員ブログ

― それは“分割不足”ではなく“設計の歪み”かもしれない ―

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

Composeを書いていると、ある瞬間にこうなります。

@Composable
fun UserProfile(
name: String,
age: Int,
isFollowing: Boolean,
isLoading: Boolean,
errorMessage: String?,
onFollowClick: () -> Unit,
onRetryClick: () -> Unit,
onBackClick: () -> Unit
)

そしてこう思います。

「引数、多すぎないか?」

この違和感は正しいです。
そして多くの場合、それは単なる見た目の問題ではありません。

設計が崩れ始めているサインです。

この記事では、

  • なぜ引数が増えるのか
  • 何が問題なのか
  • どう設計すればいいのか

を整理します。

なぜ引数は増えるのか

原因はシンプルです。

UIが複雑になったから

ですが、本質はもう一段深いです。

本当の原因

  • 状態が増えた
  • イベントが増えた
  • 責務が増えた

つまり、

1つのComposableに役割を詰め込みすぎている

状態です。

■ 引数が増えると何が起きるか

① 可読性が崩壊する

呼び出し側:

UserProfile(
name = user.name,
age = user.age,
isFollowing = state.isFollowing,
isLoading = state.isLoading,
errorMessage = state.error,
onFollowClick = { ... },
onRetryClick = { ... },
onBackClick = { ... }
)

何をしているUIなのか、直感的に分かりません。

② 状態の一貫性が崩れる

isLoading = true
errorMessage = "Error"

この状態は正しいのか?

👉 矛盾の温床になります

(第8回の正規化問題に逆戻り)

③ Previewが辛くなる

@Preview
fun Preview() {
UserProfile(
name = "",
age = 0,
isFollowing = false,
...
)
}

👉 状態を作るのが苦痛

④ 再利用できなくなる

引数が多い = 依存が多い

👉 他の画面で使えない

解決策①:UI Stateにまとめる

まずやるべきことはこれです。

data class UserProfileUiState(
val name: String,
val age: Int,
val isFollowing: Boolean,
val isLoading: Boolean,
val errorMessage: String?
)
@Composable
fun UserProfile(
state: UserProfileUiState,
onFollowClick: () -> Unit,
onRetryClick: () -> Unit,
onBackClick: () -> Unit
)

メリット

  • 引数が整理される
  • 状態の意味が明確になる
  • Previewしやすい

しかし、それでも増える場合

ここが重要です。

Stateにまとめても、まだ多い場合があります。

fun UserProfile(
state: UserProfileUiState,
onFollowClick: () -> Unit,
onRetryClick: () -> Unit,
onBackClick: () -> Unit,
onItemClick: (Item) -> Unit,
onDialogDismiss: () -> Unit
)

👉 まだ多い

この場合、問題は別です。

解決策②:Composableを分割する

引数が多い原因は、

責務が多いから

です。

例:分割前

  • ヘッダー
  • フォロー機能
  • リスト
  • ダイアログ

全部1つ

分割後

ProfileHeader(...)
FollowSection(...)
PostList(...)
ConfirmDialog(...)

これにより:

  • 引数が局所化
  • 責務が明確化
  • 再利用可能

解決策③:イベントをまとめる

イベントが多い場合もあります。

onFollowClick
onRetryClick
onBackClick
onItemClick

これをまとめます。

方法:Eventクラス

sealed interface UserProfileEvent {
object FollowClick : UserProfileEvent
object RetryClick : UserProfileEvent
object BackClick : UserProfileEvent
data class ItemClick(val id: String) : UserProfileEvent
}
@Composable
fun UserProfile(
state: UserProfileUiState,
onEvent: (UserProfileEvent) -> Unit
)

メリット

  • 引数削減
  • イベント管理が一元化
  • ViewModelと接続しやすい

解決策④:Slot APIを使う

構造的な複雑さの場合。

@Composable
fun ProfileLayout(
header: @Composable () -> Unit,
content: @Composable () -> Unit,
footer: @Composable () -> Unit
)

👉 柔軟性を確保

判断基準まとめ

引数が増えたときは、次を確認します。

パターン① 状態が多い

👉 Stateクラスにまとめる

パターン② 責務が多い

👉 Composableを分割する

パターン③ イベントが多い

👉 Eventにまとめる

パターン④ 構造が複雑

👉 Slot APIを使う

■ やってはいけない対処

❌ とりあえずそのまま

→ 技術的負債

❌ 引数を省略する(デフォルト値)

fun UserProfile(
isLoading: Boolean = false
)

→ 状態の意味が崩壊

❌ ViewModelを渡す

fun UserProfile(viewModel: VM)

→ 再利用・テスト・Preview全滅

■ まとめ

Composableの引数が増えるのは自然です。

しかしそれは、

設計を見直すべきタイミング

です。

本質

  • 引数の数は問題ではない
  • 責務の数が問題

解決アプローチ

  1. Stateにまとめる
  2. Composableを分割する
  3. Eventにまとめる
  4. Slotで構造を分離する

Composeは関数UIです。

だからこそ、

引数 = 設計そのもの

になります。

引数が増えたと感じたら、

それは単なるコードの問題ではなく、

👉 UI設計の再検討ポイント

です。

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