[英]Jetpack Compose State Hoisting, Previews, and ViewModels best practices
因此,似乎 Jetpack Compose 中推荐的做法是从可组合项中提升状态,使它们无状态、可重用和可测试,并允许在预览中轻松使用它们。 所以,而不是有类似的东西
@Composable
fun MyInputField() {
var text by remember { mutableStateOf("") }
TextField(value = text, onValueChange = { text = it })
}
你会像这样提升状态
@Composable
fun MyInputField(text: String, onTextChange: (String) -> Unit) {
TextField(value = text, onValueChange = onTextChange)
}
这很好,但是一些更复杂的用途呢? 让我们假设我有一个由可组合表示的屏幕,在 View 和 ViewModel 之间有多个交互。 这个屏幕被分成多个内部可组合(例如,一个用于标题,一个用于正文,然后又分为几个较小的组合)
viewModel()
,您可以手动实例化一个)并在Preview
使用此可组合(预览不支持像这样创建视图模型) 所以我看到的“最干净”的解决方案是仅在最高可组合级别实例化我的视图模型,然后仅将代表状态的val
传递给子组件,并回调到 ViewModel 函数。
但这很疯狂,我没有将所有 ViewModel 状态和函数通过各个参数传递给所有需要它们的组合。 例如,将它们分组在一个data class
中可能是一个解决方案
data class UiState(
val textInput: String,
val numberPicked: Int,
……
也许为回调创建另一个? 但这仍然创建了一个全新的类,只是为了模仿视图模型已经拥有的东西。
我实际上没有看到这样做的最佳方式是什么,而且我在任何地方都找不到
Jetmagic 是一个开源框架,它正好解决了这个问题,同时也解决了 Google 在开发 Compose 时忽略的其他主要问题。 关于您的请求,您根本不将视图模型作为参数传递。 Jetmagic 遵循“提升状态”模式,但它会为您管理视图模型并使它们与您的可组合项相关联。 它以类似于旧视图系统处理 xml 布局的方式将可组合项视为资源。 您不是直接调用可组合函数,而是要求 Jetmagic 的框架为您提供与设备配置最匹配的可组合函数的“实例”。 请记住,在旧的基于 xml 的系统下,您可以有效地为同一个屏幕设置多个布局(例如一个用于纵向模式,另一个用于横向模式)。 Jetmagic 会为您挑选合适的。 当它这样做时,它会为您提供一个对象,用于管理可组合的状态及其相关的视图模型。
您可以轻松访问屏幕层次结构中任何位置的视图模型,而无需将视图模型作为参数向下传递到层次结构。 这部分是使用 CompositionLocalProvider 完成的。
Jetmagic 旨在处理构成屏幕的顶级组合。 在可组合层次结构中,您仍然像往常一样调用可组合对象,但在有意义的地方使用状态提升。
最好的办法是下载 Jetmagic 并尝试一下。 它有一个很棒的演示,可以说明您正在寻找的解决方案:
管理复杂状态的一个好方法是将所需的复杂行为封装到一个类中并使用记住函数,同时尽可能多地使用无状态小部件,并在需要时更改状态的任何属性。
SearchTextField
是一个仅使用状态提升的组件, SearchBar
有后退箭头和SearchTextField
,而且它本身也是一个无状态组合。 这两者与 Searchbar 的父级之间的通信仅通过回调函数处理,这使得 SearchTextField 可重新启用并且易于在预览中使用默认状态进行预览。 HomeScreen
包含此状态以及您管理更改的位置。
完整的实现发布在这里。
fun <R, S> rememberSearchState(
query: TextFieldValue = TextFieldValue(""),
focused: Boolean = false,
searching: Boolean = false,
suggestions: List<S> = emptyList(),
searchResults: List<R> = emptyList()
): SearchState<R, S> {
return remember {
SearchState(
query = query,
focused = focused,
searching = searching,
suggestions = suggestions,
searchResults = searchResults
)
}
}
记住保持状态的功能,以便仅在组合期间进行评估。
@Composable
class SearchState<R, S>(
query: TextFieldValue,
focused: Boolean,
searching: Boolean,
suggestions: List<S>,
searchResults: List<R>
) {
var query by mutableStateOf(query)
var focused by mutableStateOf(focused)
var searching by mutableStateOf(searching)
var suggestions by mutableStateOf(suggestions)
var searchResults by mutableStateOf(searchResults)
val searchDisplay: SearchDisplay
get() = when {
!focused && query.text.isEmpty() -> SearchDisplay.InitialResults
focused && query.text.isEmpty() -> SearchDisplay.Suggestions
searchResults.isEmpty() -> SearchDisplay.NoResults
else -> SearchDisplay.Results
}
}
并通过将状态传递给其他可组合或由 ViewModel 作为更改 UI 的任何部分的状态
fun HomeScreen(
modifier: Modifier = Modifier,
viewModel: HomeViewModel,
navigateToTutorial: (String) -> Unit,
state: SearchState<TutorialSectionModel, SuggestionModel> = rememberSearchState()
) {
Column(
modifier = modifier.fillMaxSize()
) {
SearchBar(
query = state.query,
onQueryChange = { state.query = it },
onSearchFocusChange = { state.focused = it },
onClearQuery = { state.query = TextFieldValue("") },
onBack = { state.query = TextFieldValue("") },
searching = state.searching,
focused = state.focused,
modifier = modifier
)
LaunchedEffect(state.query.text) {
state.searching = true
delay(100)
state.searchResults = viewModel.getTutorials(state.query.text)
state.searching = false
}
when (state.searchDisplay) {
SearchDisplay.InitialResults -> {
}
SearchDisplay.NoResults -> {
}
SearchDisplay.Suggestions -> {
}
SearchDisplay.Results -> {
}
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.