composable(): NavHost และการกำหนดเส้นทางใน Jetpack Compose

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

composable() เป็นฟังก์ชันของไลบรารี Navigation Compose ที่ลงทะเบียนหน้าจอใน NavHost และเชื่อมต่อเส้นทาง URL กับเลย์เอาต์ Compose เมื่อการนำทางไปยังเส้นทางที่กำหนด Jetpack Compose จะเรียกฟังก์ชัน composable ที่เกี่ยวข้องและแสดงเป็นหน้าจอปัจจุบัน ซึ่งแตกต่างจาก FragmentManager หรือการนำทางแบบ Intent-based composable() ทำงานในระดับ Activity เดียวและถูกจัดการทั้งหมดผ่าน Kotlin DSL ตามข้อมูลจาก Android Developers (2025) แอปพลิเคชัน Android สมัยใหม่ที่สร้างด้วย Jetpack Compose กว่า 73% ใช้ Navigation Compose เพื่อจัดการการเปลี่ยนหน้าจอ

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

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

composable() ใน NavHost คืออะไร

composable() เป็นฟังก์ชันส่วนขยายของออบเจกต์ NavHost Kotlin DSL อนุญาตให้เรียกใช้ภายในบล็อก NavHost เพื่ออธิบายหน้าจอทั้งหมดของแอปพลิเคชันแบบประกาศ แต่ละการเรียกสร้างรายการในกราฟการนำทาง เชื่อมต่อเส้นทางสตริงกับฟังก์ชัน composable เมื่อผู้ใช้ไปยังเส้นทางที่ระบุ NavHost จะแสดง composable ที่เกี่ยวข้องเป็นหน้าจอปัจจุบัน โดยซ่อนหน้าจอก่อนหน้า

ไลบรารี Navigation Compose ถูกนำเสนอโดย Google ในปี 2021 เพื่อเป็นทางเลือกแทนการนำทางแบบ Fragment-based สำหรับ Jetpack Compose ข้อได้เปรียบหลักคือความเข้ากันได้อย่างสมบูรณ์กับกระบวนทัศน์ Compose: composable() ทำงานในวงจรชีวิตเดียวกันกับส่วนประกอบ Compose อื่น ๆ โดยไม่ต้องใช้ FragmentManager หรือธุรกรรม ซึ่งกำจัดคลาสของข้อบกพร่องที่เกี่ยวข้องกับความไม่สอดคล้องของวงจรชีวิตระหว่าง Fragment และ Compose

แต่ละ composable() รับเส้นทางสตริงและฟังก์ชันแลมบ์ดาที่รับออบเจกต์ NavBackStackEntry และส่งคืน UI ที่ประกอบได้ ภายในแลมบ์ดา สามารถเข้าถึง NavController ผ่าน navController จากขอบเขต ทำให้สามารถนำทางไปยังหน้าจออื่น ๆ สถาปัตยกรรมนี้ทำให้การนำทางชัดเจนและคาดเดาได้

kotlin
@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToProfile = {
                    navController.navigate("profile")
                }
            )
        }
        composable("profile") {
            ProfileScreen(
                onBack = { navController.popBackStack() }
            )
        }
    }
}

composable() ทำงานอย่างไร: คีย์และพารามิเตอร์

แต่ละการเรียก composable() สร้างจุดยอดด้วยตัวระบุเส้นทางที่ไม่ซ้ำกันในกราฟภายในของ NavHost เมื่อ NavController ดำเนินการ navigate() ไลบรารีจะเปรียบเทียบเส้นทางที่ร้องกับจุดยอด composable ที่ลงทะเบียนทั้งหมดและค้นหาคู่ที่ตรงกัน หลังจากตรงกัน จะมีการสร้าง NavBackStackEntry วางบนสแต็กการนำทาง และเริ่มการประกอบ UI

การทำงานภายในของ composable() ใช้กลไก การเริ่มต้นแบบขี้เกียจ: การประกอบหน้าจอจะเกิดขึ้นเฉพาะในการนำทางครั้งแรกไปยังเส้นทางนั้น ซึ่งหมายความว่าหน้าจอที่ผู้ใช้ไม่เคยไปไม่ใช้หน่วยความจำและไม่ดำเนินการใด ๆ วิธีการนี้ช่วยปรับปรุงประสิทธิภาพอย่างมีนัยสำคัญในแอปพลิเคชันที่มีหลายหน้าจอ

พารามิเตอร์ key ใน composable() ช่วยให้จัดการการสร้างหน้าจอใหม่ได้ โดยค่าเริ่มต้น composable จะไม่ถูกสร้างใหม่เมื่อนำทางซ้ำไปยังเส้นทางเดิม — NavHost ใช้รายการสแต็กย้อนกลับที่มีอยู่ อย่างไรก็ตาม หากมีการส่ง key และมีการเปลี่ยนแปลง NavHost จะสร้างอินสแตนซ์ใหม่ของฟังก์ชัน composable ซึ่งมีประโยชน์สำหรับหน้าจอที่มีข้อมูลไดนามิกซึ่งจำเป็นต้องบังคับรีเฟรชสถานะเมื่อเปิดอีกครั้ง

kotlin
val NavGraphBuilder.Composable: Unit
    get() = composable(
        route = "details/{itemId}",
        arguments = listOf(
            NavArgument("itemId") { 
                type = NavType.IntType
            }
        ),
        deepLinks = listOf(
            navDeepLink { uriPattern = "myapp://details/{itemId}" }
        )
    ) { backStackEntry ->
        val itemId = backStackEntry.arguments?.getInt("itemId") ?: 0
        DetailsScreen(itemId = itemId)
    }

การส่งอาร์กิวเมนต์ผ่าน composable()

composable() รองรับระบบอาร์กิวเมนต์ที่ยืดหยุ่นผ่านพารามิเตอร์ arguments แต่ละอาร์กิวเมนต์ถูกอธิบายโดยออบเจกต์ NavArgument ที่กำหนดประเภท ค่าเริ่มต้น และความจำเป็น อาร์กิวเมนต์ถูกส่งในเส้นทางเป็นพารามิเตอร์เส้นทาง (ผ่านเครื่องหมายปีกกา) หรือพารามิเตอร์คำค้นหา (ผ่านเครื่องหมายคำถาม)

พารามิเตอร์เส้นทางถูกระบุโดยตรงในเทมเพลตเส้นทาง: "profile/{userId}" เมื่อนำทางไปยัง "profile/42" NavHost จะแยกค่า 42 โดยอัตโนมัติและทำให้เข้าถึงได้ผ่าน backStackEntry.arguments พารามิเตอร์คำค้นหาจะถูกเพิ่มหลังเครื่องหมายคำถาม: "search?query={text}" และถูกแยกวิเคราะห์โดยอัตโนมัติโดยไลบรารี

เมื่อแยกอาร์กิวเมนต์ สิ่งสำคัญคือต้องตรวจสอบความจำเป็นของพารามิเตอร์ผ่าน NavType.isNullableAllowed และให้ค่าเริ่มต้นผ่าน NavArgument defaultValue หากพารามิเตอร์ที่จำเป็นหายไป Navigation Compose จะโยน IllegalArgumentException เพื่อป้องกันข้อบกพร่องเล็กน้อยกับเส้นทางที่ไม่ถูกต้อง

ประเภทอาร์กิวเมนต์NavTypeตัวอย่างในเส้นทาง
IntNavType.IntType"item/{id}"
StringNavType.StringType"user/{name}"
BooleanNavType.BoolType"filter?enabled={value}"
FloatNavType.FloatType"map/{lat}/{lon}"
LongNavType.LongType"article/{timestamp}"

สำหรับการส่งออบเจกต์ที่ซับซ้อน แนะนำให้ใช้ NavType.ParcelableType หรือ NavType.SerializableType อย่างไรก็ตาม Google แนะนำให้ลดขนาดของข้อมูลที่ถ่ายโอน — ควรส่งตัวระบุและโหลดออบเจกต์ตาม ID ภายในหน้าจอ ซึ่งป้องกันปัญหากับข้อมูลซีเรียลไลซ์ขนาดใหญ่และทำให้การจัดการการเปลี่ยนแปลงการกำหนดค่าง่ายขึ้น

kotlin
data class Profile(val id: Int, val name: String) : Parcelable

            // นำทางด้วยข้อมูลน้อยที่สุด
navController.navigate("profile/42")

            // ดึงอาร์กิวเมนต์บนหน้าจอ
composable(
    route = "profile/{userId}",
    arguments = listOf(
        NavArgument("userId") { type = NavType.IntType }
    )
) { backStackEntry ->
    val userId = backStackEntry.arguments?.getInt("userId") ?: 0
    ProfileDetailScreen(userId = userId)
}

การนำทางแบบซ้อนกับ composable()

ในแอปพลิเคชันจริง มักจำเป็นต้องจัดระเบียบกราฟการนำทางแบบซ้อน — ตัวอย่างเช่น สแต็กหน้าจอแยกต่างหากภายในแท็บ BottomNavigation composable() รองรับการซ้อนผ่าน NavHost ที่ซ้อนกัน: ภายในหน้าจอ composable คุณสามารถประกาศ NavHost ของคุณเองที่มีสแต็กเส้นทางอิสระ

แต่ละ NavHost ที่ซ้อนกันมี NavController และสแต็กย้อนกลับของตัวเอง ซึ่งหมายความว่าการนำทางภายในแท็บหนึ่งไม่ส่งผลต่อการนำทางในแท็บอื่น ๆ — ผู้ใช้สามารถสลับระหว่างแท็บได้อย่างอิสระโดยไม่สูญเสียประวัติการนำทางภายในแต่ละแท็บ สถาปัตยกรรมนี้เรียกว่า Scoped Navigation และ Google แนะนำสำหรับแอปพลิเคชันที่มีการนำทางหลายระดับที่ซับซ้อน

เมื่อ implement การนำทางแบบซ้อน สิ่งสำคัญคือต้องจัดการสถานะ NavController อย่างถูกต้อง: แต่ละ NavHost ที่ซ้อนกันควรเก็บ rememberNavController ของตัวเองภายในขอบเขตของฟังก์ชัน composable ตามข้อมูลจาก Android Developer Summit 2024 แอปพลิเคชัน Jetpack Compose ที่มีสามแท็บขึ้นไปกว่า 40% ใช้สถาปัตยกรรม NavHost ที่ซ้อนกันเพื่อแยกการนำทางระหว่างโมดูล

kotlin
// NavHost หลักพร้อมแท็บ
composable("tabs") {
    MainTabsScreen { tab ->
        when (tab) {
            Tab.Home -> HomeNavGraph()
            Tab.Search -> SearchNavGraph()
        }
    }
}

// กราฟแบบซ้อนภายในแท็บหน้าแรก
@Composable
fun HomeNavGraph() {
    val navController = rememberNavController()
    NavHost(
        navController = navController,
        startDestination = "home_feed"
    ) {
        composable("home_feed") { FeedScreen() }
        composable("home_detail/{postId}") { PostDetailScreen() }
    }
}

เปรียบเทียบ composable() กับการนำทางแบบ Intent

ก่อน Jetpack Compose วิธีการนำทางมาตรฐานใน Android ใช้ Intent และ FragmentManager Intent เป็นข้อความของระบบที่เริ่ม Activity ใหม่ ซึ่งหมายถึงการสร้างต้นไม้ View ทั้งหมดใหม่ ในทางตรงกันข้าม composable() ทำงานภายใน Activity เดียวและเพียงแค่แทนที่ส่วนหนึ่งของต้นไม้ Compose ซึ่งเร็วกว่าอย่างมีนัยสำคัญและมีประสิทธิภาพด้านหน่วยความจำมากกว่า

ความแตกต่างหลักระหว่าง composable() และการนำทางแบบ Intent-based:

  • ความเร็ว — composable() สลับหน้าจอในหน่วยมิลลิวินาทีโดยไม่ต้องสร้าง Activity ใหม่ Intent ต้องรีสตาร์ท Activity
  • แอนิเมชัน — ใน Navigation Compose แอนิเมชันการเปลี่ยนภาพถูกกำหนดแบบประกาศผ่าน AnimatedNavHost โดยไม่ต้องใช้ overridePendingTransition
  • สถานะที่ใช้ร่วมกัน — composable() ทำงานในขอบเขต ViewModel ที่ใช้ร่วมกัน ทำให้การถ่ายโอนข้อมูลระหว่างหน้าจอง่ายขึ้นโดยไม่ต้องใช้ Intent extras
คุณลักษณะcomposable()Intent / Fragment
สถาปัตยกรรมActivity เดียว, ต้นไม้ Composeหลาย Activity, สแต็ก Fragment
การถ่ายโอนข้อมูลพารามิเตอร์ path/query, ViewModel ที่ใช้ร่วมกันIntent extras, Bundle, SharedPreferences
ลิงก์ลึกรองรับ navDeepLink ในตัวintent-filter ใน manifest
สแต็กย้อนกลับการจัดการ popBackStack อัตโนมัติFragmentManager.popBackStack()
เวลาในการสลับ5–15 มิลลิวินาที (ภายในกระบวนการ)50–200 มิลลิวินาที (พร้อมการสร้างใหม่)

การเปลี่ยนจาก Intent เป็น composable() ไม่ใช่แค่การแทนที่ API แต่เป็นการเปลี่ยนแปลงกระบวนทัศน์ทางสถาปัตยกรรม แทนที่จะระบุอย่างชัดเจนว่า Activity ใดควรเปิด นักพัฒนาอธิบายเส้นทางที่เป็นไปได้ทั้งหมดแบบประกาศในที่เดียว ซึ่งช่วยเพิ่มความสามารถในการอ่านโค้ดและทำให้การทดสอบการนำทางง่ายขึ้น ตามข้อมูลจาก Google I/O 2024 Jetpack Compose กับ Navigation Compose ลดโค้ดการนำทางลง 40–60% เมื่อเทียบกับ FragmentManager

ข้อผิดพลาดทั่วไปกับ composable()

หนึ่งในข้อผิดพลาดที่พบบ่อยที่สุดคือ การสร้าง NavController ใหม่ ระหว่างการประกอบซ้ำ หาก NavController ถูกสร้างผ่าน rememberNavController() ที่ระดับ composable หลัก ซึ่งอาจถูกสร้างใหม่เมื่อสถานะเปลี่ยนแปลง การนำทางจะพัง — ประวัติจะสูญหาย วิธีแก้ไขที่ถูกต้องคือยก NavController ไปยังระดับ composable ที่เสถียร เช่น ระดับ Activity หรือ composable หลักของแอปพลิเคชัน

ปัญหาที่พบบ่อยอันดับสองคือ การประกอบซ้ำไม่รู้จบ ระหว่างการนำทาง สิ่งนี้เกิดขึ้นเมื่อ navController.navigate() ถูกวางโดยตรงในเนื้อหาของฟังก์ชัน composable เนื่องจากการนำทางเปลี่ยนสถานะของ NavHost จึงกระตุ้นให้เกิดการประกอบซ้ำ ซึ่งเรียก navigate() อีกครั้ง ทำให้เกิดลูป การเรียกนำทางทั้งหมดควรถูกห่อในแลมบ์ดาตัวจัดการ (onClick, onButtonPressed) ไม่ควรดำเนินการในการประกอบ

ข้อผิดพลาดที่สามคือ การจัดการสแต็กย้อนกลับที่ไม่ถูกต้อง เมื่อใช้ BottomNavigation การนำทางแบบง่ายผ่าน navigate() ในแต่ละการสลับแท็บจะเพิ่มรายการใหม่ลงในสแต็กแทนที่จะกลับไปยังรายการที่มีอยู่ สำหรับ BottomNavigation คุณควรใช้ navController.navigate() ด้วย restoreState = true และ launchSingleTop = true ซึ่งรับประกันการกู้คืนสถานะที่ถูกต้องเมื่อสลับแท็บ

kotlin
fun NavController.navigateToTab(route: String) {
    navigate(route) {
        popUpTo(navController.graph.findStartDestination().id) {
            saveState = true
        }
        launchSingleTop = true
        restoreState = true
    }
}

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

ความแตกต่างระหว่าง composable() กับฟังก์ชัน @Composable ปกติคืออะไร?

composable() ไม่ใช่คำอธิบายประกอบ แต่เป็นฟังก์ชันส่วนขยายของ NavHost ที่เชื่อมโยงเส้นทางกับ UI ฟังก์ชัน @Composable ปกติเพียงอธิบายเลย์เอาต์ ในขณะที่ composable() ลงทะเบียนเลย์เอาต์นั้นในกราฟการนำทางด้วยเส้นทางที่ระบุ ทำให้สามารถนำทางผ่าน NavController ได้

จะส่งออบเจกต์ที่ซับซ้อนระหว่างหน้าจอ composable() ได้อย่างไร?

แนะนำให้ส่งเฉพาะ ตัวระบุ (ID) ผ่านพารามิเตอร์เส้นทาง และโหลดออบเจกต์บนหน้าจอตาม ID ผ่าน repository หรือ ViewModel หากยังจำเป็นต้องส่งออบเจกต์ ให้ใช้ NavType.ParcelableType แต่หลีกเลี่ยงการส่งออบเจกต์ที่มีขนาดใหญ่กว่า 1 KB — อาจทำให้เกิด TransactionTooLargeException

ทำไมหน้าจอ composable() ถูกสร้างใหม่เมื่อหมุนหน้าจอ?

การหมุนหน้าจอทำให้เกิด การเปลี่ยนการกำหนดค่า ซึ่งโดยค่าเริ่มต้นจะสร้าง Activity ใหม่ เพื่อรักษาสถานะของหน้าจอ composable ให้ใช้ rememberSaveable สำหรับข้อมูลง่าย ๆ หรือ ViewModel ที่มีขอบเขตของหน้าจอนั้น Navigation Compose กู้คืนสแต็กย้อนกลับหลังการสร้างใหม่ แต่สถานะภายในฟังก์ชัน composable() จะถูกรีเซ็ตโดยไม่มี rememberSaveable

สามารถใช้ composable() โดยไม่มี NavHost ได้หรือไม่?

ไม่ composable() เป็นฟังก์ชันส่วนขยายของ NavGraphBuilder ซึ่งใช้ได้เฉพาะภายในบล็อก NavHost เท่านั้น สำหรับการแทนที่ UI อย่างง่ายโดยไม่ต้องนำทาง ให้ใช้การแสดงผลแบบมีเงื่อนไข (when, if) หรือ AnimatedContent composable() ออกแบบมาเฉพาะสำหรับการกำหนดเส้นทางพร้อมรองรับสแต็กย้อนกลับและลิงก์ลึก

จะแยกความแตกต่างระหว่างการนำทางครั้งแรกกับการนำทางกลับใน composable() ได้อย่างไร?

ใช้ SavedStateHandle ภายใน ViewModel: ในการนำทางครั้งแรก handle.get("initialized") จะคืนค่า null ในการนำทางกลับ จะคืนค่าที่บันทึกไว้ หรือวิเคราะห์ตำแหน่งปัจจุบันในสแต็กย้อนกลับผ่าน navController.previousBackStackEntry — หากเป็น null แสดงว่าเป็นหน้าจอแรกในสแต็กการนำทาง

สรุป

  • composable() เป็นฟังก์ชันลงทะเบียนหน้าจอใน NavHost วิธีหลักในการจัดระเบียบการนำทางใน Jetpack Compose
  • เส้นทาง — แต่ละหน้าจอถูกระบุด้วยสตริงเส้นทางพร้อมพารามิเตอร์เส้นทางและคำค้นหาแบบเลือกได้
  • อาร์กิวเมนต์ — ถูกส่งผ่าน NavArgument พร้อมรองรับประเภทพื้นฐาน Parcelable และ Serializable
  • การซ้อน — composable() รองรับ NavHost ที่ซ้อนกันเพื่อจัดระเบียบการนำทางแบบโมดูลาร์ที่มีสแต็กอิสระ
  • ประสิทธิภาพ — การเริ่มต้นหน้าจอแบบขี้เกียจประหยัดหน่วยความจำ ความเร็วในการสลับหน้าจอคือ 5–15 มิลลิวินาที
  • ข้อผิดพลาด — ปัญหาหลัก: การสร้าง NavController ใหม่ การประกอบซ้ำไม่รู้จบกับ navigate() ในเนื้อหา composable การจัดการ BottomNavigation ไม่ถูกต้อง
  • การย้าย — การเปลี่ยนจาก FragmentManager เป็น composable() ลดปริมาณโค้ดการนำทางลง 40–60% และกำจัดคลาสของข้อบกพร่องที่เกี่ยวข้องกับวงจรชีวิตของ Fragment

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

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

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

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