在Jetpack Compose中停止传递事件/UI-Action回调

在Jetpack Compose中停止传递事件/UI-Action回调

对于大多数屏幕而言,UDF对于事件的传递是低效的。

重要说明: 在本文中,假设您已经熟悉UDF(单向数据流)和MVI架构。

在Google的文档中,他们建议我们使用UDF来增加组件的可测试性和可重用性。例如…

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(text = topAppBarText)             
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {/*...*/}
        },
    )
}

通过向组件仅提供其所需的数据topAppBarText,并使用lambda函数onBackPressed来收集UI操作,我们使得该组件具有了可重用性且易于测试。这很棒,对吧?

然而,当您将该原则应用于整个屏幕时,您将很快发现需要从顶部组件一路传递20-30个事件回调到底部组件。让我举个例子 😃

@Composable
fun HomeScreen(
    onItemClick: (String) -> Unit,
    onSearchClick: () -> Unit,
    /* 更多状态和回调 */
) {
    Home(
        onItemClick = onItemClick,
        onSearchClick = onSearchClick,
        /* 更多状态和回调 */
    )
}
@Composable
private fun Home(
    onItemClick: (String) -> Unit,
    onSearchClick: () -> Unit,
    /* 更多状态和回调 */
) {
    HomeContent(
        onItemClick = onItemClick,
        onSearchClick = onSearchClick,
        /* 更多状态和回调 */
    )
}
@Composable
private fun HomeContent(
    onItemClick: (String) -> Unit,
    onSearchClick: () -> Unit,
    /* 更多状态和回调 */
) {
    Row { Button(onClick = onSearchClick) { /***/ } }
    HomeList(
        onItemClick = onItemClick,
        /* 更多状态和回调 */
    )
}
@Composable
private fun HomeList(onItemClick: (String) -> Unit) {
    items.forEach { item ->
        HomeListItem(
            onItemClick = onItemClick
        )
    }
}
@Composable
private fun HomeListItem(onItemClick: (String) -> Unit) {
    Button(onClick = { onItemClick(item) }) {
        /***/
    }
}

在这里,您会注意到我们需要像900次那样传递onItemClick/onSearchClick,这只是一个小例子,在实际项目中,相同回调的数量可能会让您觉得回到XML并不是一个坏主意 😃

另外,请查看Google示例中的此示例,请跟踪回调直至底部和顶部(HomeRoute和JetnewsNavGraph),以获取更多乐趣 🫠

UDF对于可重用/通用组件确实是一个很好的方法,但出于以下3个原因,它不适用于整个屏幕…

  1. 除非同一屏幕可重用于创建和更新操作,否则99%的屏幕都不可重用。
  2. 几乎所有屏幕的子组件都是特定于该屏幕,不能在其他地方重用。
  3. 由于开发人员懒惰,他们不会将组件提取为更小的组件,以避免传递所有这些回调的痛苦。结果,代码将变得难以阅读和维护。

解决方案

解决方案很简单,如果您正在使用MVI,您已经为所有您的意图/操作拥有一个密封类,并在ViewModel中处理它们的onAction()/onIntent()/processIntents()函数。只需传递那个onAction()函数。

注意:即使您没有使用MVI,您仍然可以使用这个解决方案。

@Composable
fun HomeScreen(
    onAction: (UiAcion) -> Unit
) {
    Home(onAction = onAction)
}
@Composable
private fun Home(onAction: (UiAcion) -> Unit) {
    HomeContent(onAction = onAction)
}
@Composable
private fun HomeContent(onAction: (UiAcion) -> Unit) {
    Row { 
        Button(onClick = { onAction(UiAction.OnSearchClick) }) {
           /***/ 
        } 
    }
    HomeList(onAction = onAction)
}
@Composable
private fun HomeList(onAction: (UiAcion) -> Unit) {
    items.forEach { item ->
        HomeListItem(
            item = item,
            onAction = onAction,
        )
    }
}
@Composable
private fun HomeListItem(
    item: String,
    onAction: (UiAcion) -> Unit,
) {
    Button(onClick = { onAction(UiAcion.OnItemClick(item)) }) { /**/ }
}

为什么这样更好?

  • 现在只有一个事件回调传递,而不是900次。
  • 开发时间更快,开发人员更快乐。
  • 您将鼓励您的团队将大型组件拆分为多个子组件,因为没有痛苦。
  • 调试变得更容易。比起浏览Action触发的位置,沿着树形结构逐个组件导航,您会更快地找到触发点,而不至于迷失方向。
  • 增强的代码可读性。
  • 仅传递函数引用,即viewModel::onAction,而不是多个lambda,将在性能上稍微更好,特别是当您在lambda内部使用不稳定的类(如viewModel)时。请查看此视频获取更多信息。

如果您需要使您的屏幕或子组件可重用,只需进行重构。替换onAction为实际回调可能需要5到10分钟,但根据我的经验,这种情况很少,并不值得进行过度优化。

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Calvin880828

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值