0
点赞
收藏
分享

微信扫一扫

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

穆风1818 03-23 19:30 阅读 3

在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分钟,但根据我的经验,这种情况很少,并不值得进行过度优化。

举报

相关推荐

0 条评论