Composeのバケツリレーが限界? CompositionLocalでスマートに解決

etpack Composeで階層が深くなると発生する「バケツリレー(Prop Drilling)」。

「自分(中間コンポーネント)は使わないのに、孫に渡すためだけに引数を定義する」という不毛な作業は、CompositionLocal でスマートにショートカットしましょう。

🧑🏻‍💻 1. CompositionLocal の仕組み

通常、データは「親 → 子 → 孫」と引数で手渡ししますが、CompositionLocal はツリー全体にデータを「漂わせる」イメージです。

下の階層にいるコンポーネントは、必要なときにそのデータを「キャッチ」するだけで済みます。

🧑🏻‍💻 2. 実装例:2つのデータ(名前と色)を孫まで飛ばす

「ユーザー名」と「テーマカラー」の2つのデータを、中間の「子」を介さずに「孫」へ届けます。

① データの「鍵」を定義する

まず、共有したいデータの種類ごとに CompositionLocal オブジェクトを作成します。


import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.graphics.Color

// 1. 各データの「鍵」を作成(デフォルト値を設定)
val LocalUserName = staticCompositionLocalOf { "ゲスト" }
val LocalUserColor = staticCompositionLocalOf { Color.Black }

② 親・子・孫の実装

中間の ChildView が引数を一つも受け取っていない点に注目してください。


@Composable
fun ParentScreen() {
    val name = "桃太郎"
    val brandColor = Color(0xFFFF5722) // オレンジ

    // 2. CompositionLocalProvider で値を注入(複数一気に指定可能)
    CompositionLocalProvider(
        LocalUserName provides name,
        LocalUserColor provides brandColor
    ) {
        // 子を呼び出す(引数で渡す必要なし!)
        ChildView()
    }
}

// --- 中間のコンポーネント ---
@Composable
fun ChildView() {
    // 自身はデータを使わないので、引数も処理もスッキリ!
    println("ChildView: 私は何も知りません。")
    GrandChildView()
}

// --- データの使い道がある末端(孫) ---
@Composable
fun GrandChildView() {
    // 3. .current を使って必要なデータだけを直接取得
    val name = LocalUserName.current
    val color = LocalUserColor.current

    Column {
        Text(text = "こんにちは、${name}さん!", color = color)
        Text(text = "親から直接データを受け取りました。")
    }
}

🧑🏻‍💻 3. なぜ「2方向」でも楽なのか?

バケツリレーの場合、渡す項目が「名前」「色」「権限」「ID」と増えるたびに、ルートから末端までの全関数の引数を書き直す必要があります。

CompositionLocal なら:
親: provides を追加するだけ

子: 修正不要(ここが最大のメリット!)

孫: .current で取り出すだけ

🧑🏻‍💻 4. 注意点:使いすぎに注意!

魔法のように便利な CompositionLocal ですが、使いすぎると「このコンポーネントは何に依存しているのか?」がコードから読み取りにくくなります。

推奨: アプリ全体のテーマ(色・フォント)、ログインユーザー情報、ロケール設定など。

非推奨: その画面内の特定のボタンでしか使わないような一時的なフラグ。

バケツリレーが3階層を超え、中間コンポーネントが「ただの運び屋」になっていたら、CompositionLocal への切り替え時かもしれません。


Related Categories :  AndroidJetpackComposeKotlinNewbieTrending