如何避免 Jetpack Compose 中的支柱钻孔

问题描述 投票:0回答:1

我将尝试用一个简短的例子来描述问题。

假设我们有一个代表自定义开关的子可组合项,并且我们希望保持它不可变,因此我们需要传递初始状态和 lambda,以在用户切换开关时更改其真实来源:

@Composable
fun CustomSwitch(
    title: String? = null,
    checked: Boolean = false,
    onSwitchChanged: ((Boolean) -> Unit)? = null
){
    //...
}

我们现在有一个父可组合项,代表屏幕上有许多开关的部分。如果我们也想保持它不可变,我们需要通过参数向上公开子级的所有属性:

@Composable
fun PreferencesCard(
    switch1Title: String? = null,
    switch1Checked: Boolean = false,
    OnSwitch1Changed: ((Boolean) -> Unit)? = null,
    switch2Title: String? = null,
    switch2Checked: Boolean = false,
    OnSwitch2Changed: ((Boolean) -> Unit)? = null,
){
    CustomSwitch(switch1Title, switch1Checked, OnSwitch1Changed)

    CustomSwitch(switch2Title, switch2Checked, OnSwitch2Changed)

    //Other composables
}    

免责声明:在这个例子中,这个

PreferencesCard
可组合项真的很愚蠢,因为它什么也不做,它可以被
Column
或某种仅采用函数体的“开放”可组合项替换。但这只是因为我想让代码保持简单,请假设它有其他子级并且也做自己的事情。

使用这种方法,当我们在可组合项的层次结构中向上时,我们需要携带所有子参数,从而导致父参数列表非常长。这个问题是声明式 UI 框架的特征,例如在 React 中,它被称为“属性钻取”。它会产生复杂且难以维护的代码,因为子级中的任何更改(例如:添加新参数)都会导致其所有父级发生更改。它违背了封装的概念。 如果我们仍想保持父可组合项不可变,一种解决方案是封装子状态和侦听器,以便参数列表更短:

data class PreferencesCardState(val switch1Title: String?, val switch1Checked: Boolean, val switch2Title: String?, val switch2Checked: Boolean) interface PreferencesCardListener { fun onSwitch1Changed(b: Boolean): Unit fun onSwitch2Changed(b: Boolean): Unit } @Composable fun PreferencesCard( state: PreferencesCardState, listener: PreferencesCardListener ){ CustomSwitch(state.switch1Title, state.switch1Checked, {value -> listener?.onSwitch1Changed(value)}) CustomSwitch(state.switch2Title, state.switch2Checked, {value -> listener?.onSwitch2Changed(value)}) //Other composables }

从状态的角度来看,这很好,因为 Compose 足够智能,仅当子参数更改时才更改子级(具有不可变属性的数据类是稳定的)。但是使用 lambda,我们可能会遇到臭名昭著的“不稳定 lambda”问题:在 
PreferencesCard

的每次重组时,都会生成一对新的 lambda,这反过来会导致每个

CustomSwitch
可组合项的重组(lambda 被视为通过撰写为状态)。
有一个技巧可以防止由于“不稳定的 lambda”而导致的重组,即传递方法引用来代替 lambda,但这些引用(通常指向 ViewModel 中定义的变异器)仍然必须从根可组合项向下传递,除非我们愿意将整个 ViewModel 作为参数传递(这是一个不好的做法)。

那么我们如何避免 prop 钻探,同时保持所有可组合项不可变?

android kotlin android-jetpack-compose immutability prop-drilling
1个回答
0
投票

internal val LocalPreferencesCardListener = compositionLocalOf { IllegalStateException("Should be passed in root") } internal val LocalPreferencesCardState = compositionLocalOf { IllegalStateException("Should be passed in root") } CompositionLocalProvider( LocalPreferencesCardListener provides vm.getListener(), LocalPreferencesCardState provides vm.getState(), ) { NestedFun { LocalPreferencesCardState.current //use for drawing state NestedFun { VeryNestedFun { LocalPreferencesCardState.current //still use for drawing state LocalPreferencesCardListener.current.invoke() } } } }

	
© www.soinside.com 2019 - 2024. All rights reserved.