在之前的文章中,我們使用 Counter 這個範例
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
StateLabTheme {
Column (
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Counter()
}
}
}
}
}
@Composable
fun Counter() {
var count by remember {
mutableIntStateOf(0)
}
Button(
onClick = {
count++
}
) {
Text("Count: $count")
}
}
Counter 這一類把 state 寫到 function 內部的方式,我們稱之為 Stateful composable function,這種方式有一個很大的問題,就是無法透過其他的 ui ,改變其狀態。
例如,我們想要新增一個按鈕,能夠重置計數,然後我們就會發現,我們無處可下手
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
StateLabTheme {
Column (
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
Counter()
// 加入 Reset 按鈕
Button(
onClick = {}
) {
Text("Reset")
}
}
}
}
}
}
...
State Hoisting
解決的方式就是 State Hoisting,把 state 向上層提升,由上層來控制,步驟大致如下:
1、將 composable function 內部的 state 改為由函式的參數
2、將 composable function 內部的行為改為由函式的 lambda 參數
@Composable
fun Counter(
count: Int,
onCounterButtonClick: () -> Unit
) {
Button(
onClick = onCounterButtonClick
) {
Text("Count: $count")
}
}
然後在上層處理變數和行為,雖然改了程式碼,但是 app 的功能和之前是相同的
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
StateLabTheme {
Column (
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
// state 變數移到此處
var count by remember {
mutableIntStateOf(0)
}
Counter(
count = count,
// 將 lambda 傳入
onCounterButtonClick = {
count++
}
)
Button(
onClick = {}
) {
Text("Reset")
}
}
}
}
}
}
...
然後,我們就可以來處理重置計數的功能。
注意! state 變數的運算,只能在傳入的 lamba 中處理,不要在 composable function 的內容中,執行 state 的運算,這會產生不必要的邊際效應。
...
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
StateLab0806Theme {
Column (
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
var count by remember {
mutableIntStateOf(0)
}
Counter(
count = count,
onCounterButtonClick = {
count++
}
)
// 加入這行後,會產生邊際效應,在按下按鈕後,開始無限增加 count
count++
Button(
onClick = {
// TODO
}
) {
Text("Reset")
}
}
}
}
}
}
...
加入 Reset 功能
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
StateLabTheme {
Column (
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
var count by remember {
mutableIntStateOf(0)
}
Counter(
count = count,
onCounterButtonClick = {
count++
}
)
Button(
onClick = {
// 加入這行就好
count = 0
}
) {
Text("Reset")
}
}
}
}
}
}
@Composable
fun Counter(
count: Int,
onCounterButtonClick: () -> Unit
) {
Button(
onClick = onCounterButtonClick
) {
Text("Count: $count")
}
}
Comments