关于Jetpack Compose状态提升中子组件未返回值却能更新父组件状态的原理疑问
Great question—this is exactly the kind of "wait, how does that even work?" moment that feels confusing when you’re new to Jetpack Compose, especially coming from more traditional UI frameworks. Let’s break this down in plain terms.
The magic is in closures (not just plain lambdas)
In your working code, the lambda { count++ } you pass to the Child isn’t just a throwaway function—it’s a closure. Closures are special functions that "remember" the variables from the scope where they were created.
Here’s the key: this lambda was defined inside the Parent composable, so it has direct access to the count mutableState variable that lives there. When the Child’s Button calls buttonPressed (which is this lambda), it doesn’t send a value back to the Parent—it directly modifies the Parent’s count variable right then and there.
Let’s walk through the full flow step by step:
- Parent holds the source of truth:
var count by rememberSaveable { mutableStateOf(0) }creates a state variable that Compose actively watches for changes. - Parent shares state and a change handler with Child: You pass two things to the Child: the current
countvalue (to display in the Text) and the lambda{ count++ }(a way to trigger an increment). - User clicks the button: The Button’s
onClicktriggers the lambda. Since it’s a closure, it increments the exactcountvariable stored in the Parent’s scope. - Compose updates the UI automatically: Because
countis amutableStateOf, Compose detects the change immediately. It then recomposes only the parts of the UI that depend oncount—in your case, theText("Clicked $count times")in the Child. The Button’s own Text doesn’t change, so that part skips recomposition (which is why yourprintln("here")only runs once—Compose’s smart recomposition at work!).
Why your alternative approach didn’t work
When you tried Button(onClick = Parent(increaseBy=1)), you weren’t updating the existing Parent’s state—you were creating a brand new instance of the Parent composable every time the button was clicked. That new Parent has its own separate count state (starting at 0), which has no connection to the original Parent that’s actually rendered on the screen. Compose’s UI is built around recomposing existing components when state changes, not manually spawning new ones like this.
Your conclusion was mostly right (with a small tweak)
You nailed the intentional design part: state hoisting keeps state in a single source of truth, makes components reusable (your Child doesn’t need to know how the Parent manages state—it just gets data and a way to request a change), and keeps your code clean and decoupled.
The lambda isn’t just triggering a "test" though—it’s directly modifying the parent’s state variable. Compose’s recomposition system then takes over to update the parts of the UI that need it.
This pattern might feel odd at first, but once you get comfortable with closures and state hoisting, it becomes a powerful way to build maintainable Compose UIs!
内容来源于stack exchange

