State Hoisting: การยกสถานะและโฟลว์ข้อมูลทิศทางเดียวใน Compose

ผู้แต่ง: IT Sectr เผยแพร่เมื่อ: 2026-06-28 เวลาอ่าน: 8 นาที

State Hoisting เป็นรูปแบบใน Jetpack Compose ที่สถานะถูกย้ายออกจากฟังก์ชัน Composable ลูกไปยังฟังก์ชันแม่ และลูกจะได้รับข้อมูลผ่านพารามิเตอร์และแจ้งการเปลี่ยนแปลงผ่าน Callback นี่คือการนำหลักการโฟลว์ข้อมูลทิศทางเดียว (UDF) มาใช้ โดยสถานะถูกยกขึ้นและอีเวนต์ถูกส่งลงมา ตามข้อมูลจาก Google Android Developers, 2026 พบว่า State Hoisting ทำให้คอมโพเนนต์สามารถนำกลับมาใช้ใหม่ ทดสอบได้ และคาดเดาได้

ประเด็นสำคัญ

  • State Hoisting ย้ายสถานะจากคอมโพเนนต์ลูกไปยังคอมโพเนนต์แม่
  • UDF (โฟลว์ข้อมูลทิศทางเดียว) — สถานะไหลลง อีเวนต์ไหลขึ้น
  • พารามิเตอร์ ของคอมโพเนนต์ลูก: ค่า (T) + แลมบ์ดา (T) -> Unit
  • การนำกลับมาใช้ใหม่ — สถานะที่ถูกยกขึ้นทำให้ใช้ฟังก์ชันเดียวกันกับแหล่งข้อมูลต่าง ๆ ได้
  • การทดสอบ — State Hoisting ช่วยลดความซับซ้อนของการทดสอบหน่วยโดยแยกตรรกะออกจาก UI

State Hoisting ใน Jetpack Compose คืออะไร

State Hoisting เป็นรูปแบบที่ฟังก์ชัน Composable ไม่ได้เป็นเจ้าของสถานะ แต่ได้รับจากภายนอก แทนที่จะใช้ var ภายในฟังก์ชัน จะใช้พารามิเตอร์สองตัว: ค่าสำหรับแสดงผลและ Callback แลมบ์ดาสำหรับจัดการการเปลี่ยนแปลง ในทางเทคนิค หมายความว่าคอมโพเนนต์ลูกกลายเป็นไร้สถานะ (stateless) ในขณะที่แม่มีสถานะ (stateful)

ตัวอย่าง: คอมโพเนนต์ TextField จาก Material3 ไม่ได้จัดเก็บข้อความที่ป้อนไว้ภายใน โดยรับ value: String และ onValueChange: (String) -> Unit แม่ที่เรียก TextField ประกาศ var value by remember { mutableStateOf("") } และส่ง value และ onValueChange นี่คือ State Hoisting แบบคลาสสิก: TextField เป็นคอมโพเนนต์ธรรมดา (เพียงแสดงและรายงานการป้อนข้อมูล) แม่เป็นคอมโพเนนต์อัจฉริยะ (เป็นเจ้าของสถานะ)

ไร้สถานะเทียบกับมีสถานะ: คอมโพเนนต์ไร้สถานะทดสอบได้ง่ายกว่า — ไม่ขึ้นอยู่กับสถานะภายใน พฤติกรรมของมันถูกกำหนดโดยพารามิเตอร์อินพุตทั้งหมด คอมโพเนนต์มีสถานะสะดวกสำหรับการสร้างต้นแบบอย่างรวดเร็วแต่ยากต่อการนำกลับมาใช้ใหม่: มันผูกติดกับแหล่งข้อมูลเดียวอย่างแน่นหนา State Hoisting ให้คุณเลือก: คอมโพเนนต์ใด ๆ ก็สามารถทำให้ไร้สถานะได้โดยการย้ายสถานะขึ้นไป

โฟลว์ข้อมูลทิศทางเดียว (UDF) และ State Hoisting

UDF (โฟลว์ข้อมูลทิศทางเดียว) เป็นหลักการทางสถาปัตยกรรมที่ข้อมูลเคลื่อนที่ในทิศทางเดียว: จากแหล่งความจริง (ViewModel หรือ Composable แม่) ไปยัง UI และอีเวนต์ไหลในทิศทางตรงกันข้าม State Hoisting คือการนำ UDF ไปใช้ในระดับคอมโพเนนต์แต่ละตัว แทนที่คอมโพเนนต์แต่ละตัวจะตัดสินใจว่าเมื่อไรและอย่างไรที่จะเปลี่ยนสถานะของตัวเอง มันจะแจ้งให้แม่ทราบเกี่ยวกับอีเวนต์ และแม่ตัดสินใจว่าจะเปลี่ยนสถานะอย่างไร

ข้อดีของ UDF: ความสามารถในการคาดเดา — สถานะเปลี่ยนในที่เดียวเท่านั้น กำจัดสภาวะการแข่งขัน; การติดตามได้ — สแตกการเรียกช่วยให้สามารถสร้างห่วงโซ่การเปลี่ยนแปลงขึ้นใหม่ได้; การทดสอบ — ตรรกะที่มีสถานะสามารถแยกออกเป็นคลาสแยกต่างหากและทดสอบโดยไม่มี UI ในโปรเจกต์ขนาดใหญ่ UDF ที่รวมกับ State Hoisting เป็นมาตรฐานโดยพฤตินัย

แหล่งความจริงเดียว (Single Source of Truth) เป็นอีกหลักการหนึ่งที่มาพร้อมกับ UDF แต่ละส่วนของสถานะมีแหล่งที่มาเพียงแหล่งเดียว หากสองคอมโพเนนต์ใช้สถานะเดียวกัน แหล่งที่มาควรถูกแชร์ (ในระดับ ViewModel หรือแม่ร่วม) State Hoisting ทำให้แน่ใจว่าแหล่งที่มาอยู่ด้านบนในลำดับชั้นและไม่เกิดการซ้ำซ้อนของสถานะ

ทิศทางสิ่งที่ถูกส่งวิธีนำไปใช้
ลง (แม่ → ลูก)ค่าสำหรับแสดงพารามิเตอร์ value: T
ขึ้น (ลูก → แม่)อีเวนต์การเปลี่ยนแปลงพารามิเตอร์ onValueChange: (T) -> Unit

กฎของ State Hoisting: เมื่อไรและอย่างไรที่จะยกสถานะ

กฎหลัก: สถานะควรถูกยกขึ้นไปยังระดับต่ำสุดที่เป็นไปได้เพียงพอสำหรับคอมโพเนนต์ทั้งหมดที่ต้องการ หากสถานะถูกใช้ภายในคอมโพเนนต์เดียวเท่านั้น — ให้เก็บไว้เฉพาะที่ หากคอมโพเนนต์สองตัวที่อยู่ติดกันต้องการสถานะเดียวกัน — ยกขึ้นไปยังแม่ร่วม หากสถานะจำเป็นทั่วทั้งหน้าจอ — ยกขึ้นไปยัง ViewModel

กฎการยกขั้นต่ำ ป้องกันความซับซ้อนที่ไม่จำเป็น ไม่มีประโยชน์ที่จะยกสถานะของช่องข้อความไปยัง ViewModel หากใช้ภายในหน้าจอเดียวเท่านั้นและไม่คงอยู่เมื่อ Activity ถูกสร้างใหม่ ใช้ rememberSaveable ในระดับแม่ของหน้าจอ ไม่ใช่ ViewModel สำหรับสถานะ UI ที่ควรรอดจากการหมุนหน้าจอแต่ไม่จำเป็นต้องใช้โดยตรรกะทางธุรกิจ

เมื่อไรที่จะยกไปยัง ViewModel: หากสถานะต้องคงอยู่หลังจากการสร้าง Activity ใหม่ หากจำเป็นต้องใช้โดยหลายหน้าจอ หากการเปลี่ยนสถานะกระตุ้นตรรกะทางธุรกิจ (คำขอเครือข่าย ฐานข้อมูล) State Hoisting ในระดับ ViewModel เป็นรูปแบบมาตรฐานในสถาปัตยกรรม MVVM ที่ชั้น UI เป็นไร้สถานะและ ViewModel มีสถานะ

kotlin
    // ❌ ไม่ดี: คอมโพเนนต์เป็นเจ้าของสถานะของตัวเอง
@Composable
fun BadTextField(label: String) {
    var text by remember { mutableStateOf("") }
    TextField(value = text, onValueChange = { text = it }, label = { Text(label) })
}

    // ✅ ดี: State Hoisting — สถานะอยู่ในแม่
@Composable
fun GoodTextField(value: String, onValueChange: (String) -> Unit, label: String) {
    TextField(value = value, onValueChange = onValueChange, label = { Text(label) })
}

    // การใช้งาน: แม่เป็นเจ้าของสถานะ
@Composable
fun Form() {
    var name by rememberSaveable { mutableStateOf("") }
    GoodTextField(value = name, onValueChange = { name = it }, label = "Name")
}

ตัวอย่าง State Hoisting ในคอมโพเนนต์จริง

พิจารณาหน้าจอเข้าสู่ระบบที่มีสองช่อง (อีเมล รหัสผ่าน) และปุ่มหนึ่งปุ่ม คอมโพเนนต์ทั้งสามรับสถานะผ่าน State Hoisting: อีเมลและรหัสผ่านถูกจัดการโดยแม่ ปุ่มรับสถานะ enabled เป็นค่า

kotlin
    // State Hoisting ในระดับหน้าจอ
@Composable
fun LoginScreen(viewModel: LoginViewModel) {
    val uiState by viewModel.uiState.collectAsState()

    Column(modifier = Modifier.padding(16.dp)) {
        // ช่องอีเมล — State Hoisting ผ่านแลมบ์ดา
        EmailField(
            email = uiState.email,
            onEmailChange = { viewModel.onEmailChanged(it) }
        )

        // ช่องรหัสผ่าน — เช่นเดียวกัน
        PasswordField(
            password = uiState.password,
            onPasswordChange = { viewModel.onPasswordChanged(it) }
        )

        // ปุ่ม — รับเฉพาะ enabled (อ่านอย่างเดียว)
        LoginButton(enabled = uiState.isFormValid, onClick = viewModel::login)
    }
}

// คอมโพเนนต์ไร้สถานะ: รับอีเมล + Callback
@Composable
fun EmailField(email: String, onEmailChange: (String) -> Unit) {
    OutlinedTextField(
        value = email,
        onValueChange = onEmailChange,
        label = { Text("อีเมล") },
        singleLine = true
    )
}

// คอมโพเนนต์ปุ่มไร้สถานะ
@Composable
fun LoginButton(enabled: Boolean, onClick: () -> Unit) {
    Button(onClick = onClick, enabled = enabled) {
        Text("เข้าสู่ระบบ")
    }
}

EmailField และ PasswordField ไร้สถานะโดยสมบูรณ์ สามารถนำกลับมาใช้ใหม่บนหน้าจอใดก็ได้โดยเชื่อมต่อกับแหล่งข้อมูลใดก็ได้ LoginButton รับ enabled เป็นแบบอ่านอย่างเดียว — นี่เป็นอีกรูปแบบหนึ่งของ State Hoisting ที่สถานะไม่ได้ถูกยกขึ้น (ปุ่มไม่สามารถเปิดใช้งานตัวเองได้) แต่ถูกส่งลงมาแบบพร้อมใช้งาน วิธีการนี้ให้ความยืดหยุ่นสูงสุดด้วยการผูกพันระหว่างคอมโพเนนต์น้อยที่สุด

State Hoisting เทียบกับสถานะเฉพาะที่: เกณฑ์การเลือก

ไม่ใช่ทุกสถานะที่จำเป็นต้องยกขึ้น สถานะเฉพาะที่ (State ภายใน Composable) มีความชอบธรรมเมื่อ: ข้อมูลจำเป็นภายในคอมโพเนนต์เดียวเท่านั้น ไม่ส่งผลกระทบต่อองค์ประกอบข้างเคียง และไม่ควรคงอยู่หลังจากการประกอบใหม่ของส่วนเฉพาะ ตัวอย่างเช่น สถานะแอนิเมชัน โฟกัสของช่องป้อนข้อมูล ตำแหน่งการเลื่อนปัจจุบัน — การเก็บไว้เฉพาะที่เป็นเรื่องสมเหตุสมผล

เมื่อไรที่ State Hoisting จำเป็น: สถานะถูกใช้โดยคอมโพเนนต์ลูกหลายตัว; การเปลี่ยนแปลงในลูกหนึ่งควรสะท้อนในอีกลูกหนึ่ง; ตรรกะของการเปลี่ยนแปลงสถานะต้องถูกทดสอบแยกจาก UI; สถานะควรคงอยู่หลังจากการสร้าง Activity ใหม่ ในกรณีเหล่านี้ สถานะเฉพาะที่สร้างความซ้ำซ้อนและความไม่สอดคล้องของข้อมูล

วิธีการแบบผสม: เก็บสถานะขั้นต่ำไว้เฉพาะที่ ยกส่วนที่เหลือ กฎของ Compose: “ยกสถานะให้สูงเท่าที่จำเป็นและต่ำเท่าที่เป็นไปได้” ในทางปฏิบัติ หมายถึงเริ่มต้นด้วย remember เฉพาะที่ และยกระดับเมื่อจำเป็นต้องเข้าถึงจากคอมโพเนนต์อื่นเท่านั้น อย่าใช้ State Hoisting เชิงป้องกัน — มันทำให้โค้ดซับซ้อนโดยไม่จำเป็น

คำถามที่พบบ่อย

State Hoisting แตกต่างจาก ViewModel อย่างไร?

State Hoisting เป็นรูปแบบระดับคอมโพเนนต์ UI ViewModel เป็นชั้นทางสถาปัตยกรรมสำหรับตรรกะทางธุรกิจ State Hoisting สามารถยกสถานะไปยังระดับ Composable แม่ ระดับหน้าจอ หรือ ViewModel ViewModel คือจุดยกสูงสุดสำหรับสถานะที่ต้องคงอยู่หลังจากการสร้าง Activity ใหม่

วิธีทดสอบคอมโพเนนต์ที่มี State Hoisting?

คอมโพเนนต์ไร้สถานะถูกทดสอบโดยเพียงแค่ส่งค่า เรียก Composable ด้วยพารามิเตอร์ที่ต้องการและตรวจสอบการแสดงผลผ่าน ComposeTestRule การเปลี่ยนแปลงสถานะถูกทดสอบในระดับแม่หรือ ViewModel — แยกจาก UI ซึ่งช่วยลดความซับซ้อนของการทดสอบได้อย่างมาก: ไม่จำเป็นต้องจำลองการประกอบใหม่ภายในคอมโพเนนต์

สามารถยกสถานะแบบอ่านอย่างเดียวได้หรือไม่?

ได้ เป็นวิธีปฏิบัติที่พบได้ทั่วไป หากคอมโพเนนต์ต้องการเพียงแสดงข้อมูลโดยไม่มีความสามารถในการแก้ไข — ส่ง State<T> (อ่านอย่างเดียว) คอมโพเนนต์จะติดตามการเปลี่ยนแปลงแต่ไม่สามารถเริ่มต้นการเปลี่ยนแปลงได้ ซึ่งเสริมสร้างการห่อหุ้มและปกป้องข้อมูลจากการกลายพันธุ์ที่ไม่พึงประสงค์

จะทำอย่างไรหากต้องยกสถานะลึก 3+ ระดับ?

สำหรับการส่งผ่านแบบลึก ใช้ CompositionLocal หรือส่งผ่านพารามิเตอร์ของ Composable แม่ หากสถานะจำเป็นทั่วทั้งหน้าจอ — แยกไปยัง ViewModel และใช้ collectAsState() การส่งผ่าน 5+ ระดับเป็นสัญญาณของสถาปัตยกรรมที่ไม่ถูกต้อง; พิจารณาลำดับชั้นของคอมโพเนนต์ใหม่

State Hoisting ส่งผลต่อประสิทธิภาพหรือไม่?

State Hoisting อาจเพิ่มจำนวนการประกอบใหม่เล็กน้อยเนื่องจากการเปลี่ยนแปลงในแม่อาจประกอบลูกทั้งหมดใหม่ ใช้ derivedStateOf เพื่อกรองการเปลี่ยนแปลงและ keys ใน LazyColumn สำหรับการอัปเดตตามเป้าหมาย ในสถานการณ์ส่วนใหญ่ โอเวอร์เฮดของ State Hoisting นั้นเล็กน้อยเมื่อเทียบกับประโยชน์ของการบำรุงรักษา

สรุป

  • State Hoisting ย้ายสถานะไปยังคอมโพเนนต์แม่ ทำให้ลูกไร้สถานะ
  • UDF รับประกันโฟลว์ข้อมูลทิศทางเดียว: สถานะลง อีเวนต์ขึ้น
  • การนำกลับมาใช้ใหม่ — คอมโพเนนต์ไร้สถานะสามารถเชื่อมต่อกับแหล่งข้อมูลใดก็ได้
  • การทดสอบ — การทดสอบ UI ตรวจสอบเฉพาะการแสดงผล ตรรกะถูกทดสอบแยกต่างหาก
  • การยกขั้นต่ำ — ยกเท่าที่จำเป็นเท่านั้น
  • ViewModel — จุดยกสูงสุดสำหรับสถานะที่มีตรรกะทางธุรกิจ
  • คำแนะนำ: เริ่มต้นด้วย remember เฉพาะที่ ยกเมื่อจำเป็นเท่านั้น

เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร

IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ

ปรึกษาโครงการ

อ่านเพิ่มเติม