在之前的文章中,我們使用 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")
    }
}

Last modified: 2025 年 8 月 6 日

Author

Comments

Write a Reply or Comment

Your email address will not be published.