这段代码为何导致重组(Jetpack Compose)?

huangapple go评论69阅读模式
英文:

Why this code is causing recomposition (jetpack compose)

问题

我不知道为什么在我的代码中,我输入“hi”的文本总是被重新组合。
文本已固定为“hi”,并且 onClick 也使用 uiEvent remember 来保持实例。

即使 uiState.isLoading 被更改,它与 Text 无关,所以我预期会跳过重新组合,但是当 Text 更改时,重新组合总是发生。

这是我的代码:

UiState 数据类

data class UiState(
    val isLoading: Boolean
)

UiEvent 接口

@Immutable
interface UiEvent {
    fun onClick()
}

可组合函数

@Composable
fun MyScreen(
    viewModel: MyViewModel = hiltViewModels()
) {
    val uiState = viewModel.uiState.collectAsStateWithLifecycle()
    val uiEvent = remember {
       object: UiEvent {
            override fun onClick() {
                viewModel.action()
            }
       }
    }
    Text(  // 为什么重新组合?
        modifier = Modifier.clickable(onClick = uiEvent::onClick),
        text = "hi"
    )
    if (uiState.isLoading) {
        Text(text = "isLoading")
    }

}
英文:

I don't know why in my code the text where I type "hi" is always recomposed.
The text was fixed to "hi", and onClick also maintained the instance using uiEvent remember.

Even if uiState.isLoading is changed, it is not related to Text, so I expected that recomposition would be skipped, but recomposition always occurs when Text is changed.

Here is my code:

UiState Data Class

data class UiState(
    val isLoading: Boolean
)

UiEvent Interface.

@Immutable
interface UiEvent {
    fun onClick()
}

Composable

@Composable
fun MyScreen(
    viewModel: MyViewModel = hiltViewModels()
) {
    val uiState = viewModel.uiState.collectAsStateWithLifecycle()
    val uiEvent = remember {
       object: UiEvent {
            override onClick() {
                viewModel.action()
            }
       }
    }
    Text(  // why recomposition?
        modifier = Modifier.clickable(onClick = uiEvent::onClick),
        text = "hi"
    )
    if (uiState.isLoading) {
        Text(text = "isLoading")
    }

}

答案1

得分: 7

I assume what you are asking is why does Text not skip whenever MyScreen recomposes. The reason is a new of the clickable modifier is being created every time. Consider remembering the entire modifier such as:

@Composable
fun MyScreen(
    viewModel: MyViewModel = hiltViewModels()
) {
    val uiState = viewModel.uiState.collectAsStateWithLifecycle()
    val modifier = remember(viewModel) {
        val event = object: UiEvent {
            override fun onClick() {
                viewModel.action()
            }
        }
        Modifier.clickable { event.onClick() }
    }
    Text(  // why recomposition?
        modifier = modifier,
        text = "hi"
    )
    if (uiState.isLoading) {
        Text(text = "isLoading")
    }
}

Note the addition of the viewModel as a parameter to the remember to ensure a new version of clickable() gets created if/when the viewModel changes.

The compose compiler plugin should recognize the :: syntax and do the remember for you, like it does for lambda syntax, but it doesn't right now. Also, the clickable() always returns a modifier that is not equal to the previous version for the same parameters, but that is going to be fixed in 1.5 (along with quite a number of other performance related fixes for clickable()). For now, you can work around these limitations by remembering the entire modifier.

英文:

I assume what you are asking is why does Text not skip whenever MyScreen recomposes. The reason is a new of the clickable modifier is being created every time. Consider remembering the entire modifier such as:

@Composable
fun MyScreen(
    viewModel: MyViewModel = hiltViewModels()
) {
    val uiState = viewModel.uiState.collectAsStateWithLifecycle()
    val modifier = remember(viewModel) {
        val event = object: UiEvent {
            override fun onClick() {
                viewModel.action()
            }
        }
        Modifier.clickable { event.onClick() }
    }
    Text(  // why recomposition?
        modifier = modifier,
        text = "hi"
    )
    if (uiState.isLoading) {
        Text(text = "isLoading")
    }
}

Note the addition of the viewModel as a parameter to the remember to ensure a new version of clickable() gets created if/when the viewModel changes.

The compose compiler plugin should recognize the :: syntax and do the remember for you, like it does for lambda syntax, but it doesn't right now. Also, the clickable() always returns a modifier that is not equal to the previous version for the same parameters, but that is going to be fixed in 1.5 (along with quite a number of other performance related fixes for clickable()). For now, you can work around these limitation by remembering the entire modifier.

答案2

得分: 1

我已经制作了一个简单的布局,包含3个文本:

  • 点击时的静态文本
  • 静态文本
  • 动态文本(带有点击计数)

简单布局

这是在3次点击后重新组合计数的样子:

重新组合

可点击的文本在每次点击后重新组合。已在Compose BOM 2023.08.00上进行了测试。

解决方法是将可点击的修饰符包装在remember中,但我不确定是否应该将其作为使用可点击修饰符的默认方式。

英文:

I have made a simple layout with 3 texts:

  • Static text with on click
  • Static text
  • Dynamic text (with clicks count)

simple layout

This is how recompositions count looks after 3 clicks:

recompositions

Clickable Text is recomposed every click. Tested on Compose BOM 2023.08.00

Workaround is to wrap clickable modifier with remember but I am not sure if it should be default way to use clickable Modifier.

huangapple
  • 本文由 发表于 2023年4月20日 09:49:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/76059969.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定