SideEffect — คืออะไร การซิงโครไนซ์สถานะใน Jetpack Compose

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

SideEffect คือฟังก์ชัน composable ใน Jetpack Compose ที่ดำเนินการบล็อกโค้ดที่ส่งผ่านในทุกครั้งที่มีการเรียกองค์ประกอบใหม่ (recomposition) ที่สำเร็จ แตกต่างจาก LaunchedEffect และ DisposableEffect ตรงที่ SideEffect ไม่ได้ผูกอยู่กับคีย์และไม่มีบล็อกการทำความสะอาด — มันเพียงแค่ซิงโครไนซ์สถานะ Compose กับระบบภายนอกหลังจากการเรนเดอร์แต่ละครั้ง สิ่งนี้ทำให้เหมาะสำหรับการอัปเดตฟังก์ชัน callback การซิงโครไนซ์กับ ViewPager และการส่งข้อมูลไปยัง Analytics SDK ตาม Android Developers Documentation (2025) ระบุว่า SideEffect จะถูกดำเนินการหลังจากที่ Compose ยืนยันการเรียกองค์ประกอบใหม่ที่สำเร็จแล้วเท่านั้น และจะไม่ถูกดำเนินการหากข้ามการเรียกองค์ประกอบใหม่

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

  • SideEffect — API เอฟเฟกต์ข้างเคียงสำหรับโค้ดที่ดำเนินการหลังจากการเรียกองค์ประกอบใหม่ที่สำเร็จแต่ละครั้ง
  • การซิงโครไนซ์ — ส่งผ่านสถานะ Compose ไปยังระบบภายนอกที่ไม่รองรับ Compose
  • ไม่มีคีย์ — แตกต่างจาก LaunchedEffect ตรงที่ SideEffect จะไม่เริ่มต้นใหม่ แต่จะดำเนินการในการเรียกองค์ประกอบใหม่ทุกครั้ง
  • ไม่มีการทำความสะอาด — SideEffect ไม่มี onDispose มันถูกออกแบบมาสำหรับการซิงโครไนซ์ทางเดียวเท่านั้น
  • ซิงโครนัส — บล็อกจะถูกดำเนินการแบบซิงโครนัสภายในเฟส composition ของ Compose โดยไม่มี coroutine

SideEffect ใน Jetpack Compose คืออะไร

SideEffect เป็น API เอฟเฟกต์ข้างเคียงที่ง่ายที่สุดใน Jetpack Compose มันดำเนินการบล็อกโค้ดในการเรียกองค์ประกอบใหม่ที่สำเร็จแต่ละครั้งของคอมโพเนนต์ composable คำว่า “สำเร็จ” เป็นสิ่งสำคัญ: ถ้า Compose ตัดสินใจว่าไม่จำเป็นต้องเรียกองค์ประกอบใหม่ (เช่น พารามิเตอร์อินพุตทั้งหมดไม่เปลี่ยนแปลงและผลลัพธ์จะเหมือนเดิม) SideEffect จะไม่ถูกดำเนินการ สิ่งนี้รับประกันว่าบล็อกการซิงโครไนซ์จะถูกเรียกเฉพาะเมื่อ UI เปลี่ยนแปลงจริง

กรณีการใช้งานหลักของ SideEffect คือการซิงโครไนซ์สถานะ Compose กับออบเจ็กต์ที่ไม่ได้เป็นส่วนหนึ่งของทรี Compose ตัวอย่างทั่วไปได้แก่: การอัปเดตฟังก์ชัน callback ในระบบ Legacy View การส่งผ่านสถานะปัจจุบันไปยัง ViewPager การส่งเหตุการณ์ไปยัง SDK วิเคราะห์เมื่อข้อมูลที่แสดงเปลี่ยนแปลง และการซิงโครไนซ์กับ SDK แผนที่ที่คาดหวังการอัปเดตในรูปแบบภายนอก

ตาม Android Developer Blog (2025) ระบุว่า SideEffect มักใช้ร่วมกับ remember: remember เก็บรักษาออบเจ็กต์ (เช่น callback) และ SideEffect อัปเดตมันเมื่อใดก็ตามที่การพึ่งพาเปลี่ยนแปลง รูปแบบนี้สำคัญเป็นพิเศษสำหรับไลบรารีที่ยอมรับออบเจ็กต์ listener และไม่สร้างใหม่เมื่ออัปเดต — หากไม่มี SideEffect listener จะเก็บรีเฟอเรนซ์ที่ล้าสมัยไปยังสถานะปัจจุบัน

kotlin
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
    val mapView = remember { MapView(LocalContext.current) }
    
    SideEffect {
        mapView.setZoom(zoomLevel)
        mapView.updateMarkers(markers)
    }
    
    AndroidView(factory = { mapView })
}

SideEffect ทำงานอย่างไรและเฟส composition

เพื่อเข้าใจ SideEffect คุณต้องเข้าใจเฟสการดำเนินการของ Jetpack Compose Compose ผ่านสามเฟสสำหรับแต่ละเฟรม: Composition (แสดงอะไร), Layout (แสดงที่ไหน), Drawing (แสดงอย่างไร) SideEffect จะถูกดำเนินการที่ส่วนท้ายของเฟส Composition — หลังจากฟังก์ชัน composable ทั้งหมดทำงานแล้ว แต่ก่อนเฟส Layout สิ่งนี้รับประกันว่า SideEffect จะเห็นสถานะสุดท้ายของตัวแปรทั้งหมดหลังจากการเรียกองค์ประกอบใหม่

ตำแหน่งในวงจรชีวิตนี้ให้ข้อได้เปรียบที่สำคัญ: SideEffect ไม่สามารถทำให้เกิดการเรียกองค์ประกอบใหม่ไม่สิ้นสุด แม้ว่าสถานะจะถูกเปลี่ยนแปลงภายในมันก็ตาม เนื่องจากมันถูกดำเนินการหลังจาก composition การเปลี่ยนแปลงที่ทำภายใน SideEffect จะถูกนำไปใช้ในเฟรมถัดไปเท่านั้น — สิ่งนี้ป้องกันรอบทั่วไปของการเปลี่ยนแปลงสถานะภายในเนื้อหาของฟังก์ชัน composable (เมื่อ setState ภายใน composition กระตุ้น composition ใหม่ก่อนที่ composition ปัจจุบันจะเสร็จสิ้น)

คุณสมบัติอีกอย่างคือ SideEffect ไม่ได้รับการปรับให้เหมาะสมด้วยคีย์ มันถูกดำเนินการในการเรียกองค์ประกอบใหม่ทุกครั้งโดยไม่คำนึงถึงว่าสถานะเฉพาะใดเปลี่ยนแปลงไป หากคุณต้องการการควบคุมที่แม่นยำยิ่งขึ้น (ดำเนินการเฉพาะเมื่อพารามิเตอร์เฉพาะเปลี่ยนแปลง) ให้ใช้ LaunchedEffect พร้อมคีย์หรือห่อ SideEffect ในการตรวจสอบการเปลี่ยนแปลงผ่าน remember

kotlin
// SideEffect optimized with remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }

SideEffect {
    if (currentZoom != zoomLevel) {
        map.animateToZoom(zoomLevel)
        currentZoom = zoomLevel
    }
}

// Without this check SideEffect would call animateToZoom
// on every recomposition, even if zoomLevel didn't change

อัปเดตฟังก์ชัน callback ด้วย SideEffect

สถานการณ์ปฏิบัติที่พบบ่อยที่สุดสำหรับ SideEffect คือการอัปเดตฟังก์ชัน callback ที่จับภาพสถานะปัจจุบัน ใน Jetpack Compose สิ่งนี้เรียกว่า “การจัดการวงจรชีวิต callback” ปัญหาคือนิพจน์ lambda ใน Kotlin จับภาพตัวแปรโดยรีเฟอเรนซ์ และถ้า callback ถูกสร้างขึ้นด้วยค่าหนึ่งและต่อมาตัวแปรเปลี่ยนไป — callback จะยังคงใช้ค่าเก่าต่อไป

พิจารณาตัวอย่าง: Google Maps SDK สำหรับ Android ยอมรับออบเจ็กต์ OnCameraMoveListener ผ่าน setOnCameraMoveListener() ถ้าคุณส่ง lambda ที่จับภาพ isTrackingEnabled เมื่อ isTrackingEnabled เปลี่ยนแปลง lambda จะไม่ถูกอัปเดต — Maps SDK จะยังคงเรียก callback เก่าด้วยข้อมูลที่ล้าสมัย SideEffect แก้ปัญหานี้: มันตั้งค่า listener ใหม่ในการเรียกองค์ประกอบใหม่ทุกครั้ง รับประกันว่า SDK จะใช้ lambda ปัจจุบันกับสถานะล่าสุดเสมอ

ตาม เอกสาร Maps SDK สำหรับ Android (2025) Google แนะนำรูปแบบนี้เมื่อรวม Maps กับ Jetpack Compose วิธีการที่คล้ายกันใช้สำหรับ WebView, VideoView, TextureView และคอมโพเนนต์ที่ใช้ View อื่นๆ ที่ยอมรับ callback ผ่านเมธอด set SideEffect รับประกันว่า callback จะเป็นปัจจุบันทุกครั้งที่มีการเปลี่ยนแปลงสถานะ

kotlin
@Composable
fun MapComposable(isTrackingEnabled: Boolean, onMarkerClick: (Marker) -> Unit) {
    val mapView = remember { MapView(LocalContext.current) }
    
    SideEffect {
        mapView.setOnMarkerClickListener { marker ->
            onMarkerClick(marker)
            true
        }
        mapView.isTrafficEnabled = isTrackingEnabled
    }
    
    AndroidView(factory = { mapView })
}

การซิงโครไนซ์กับ Analytics SDK

อีกสถานการณ์สำคัญสำหรับ SideEffect คือการส่งเหตุการณ์ไปยังระบบวิเคราะห์เมื่อสถานะ UI เปลี่ยนแปลง ตัวอย่างเช่น เมื่อผู้ใช้เปลี่ยนแท็บใน TabLayout ภายในหน้าจอ Compose SideEffect สามารถส่งผ่านสถานะแท็บที่ถูกเลือกปัจจุบันไปยัง Firebase Analytics หรือ AppsFlyer ทุกครั้งที่แท็บที่ถูกเลือกเปลี่ยน (และเกิดการเรียกองค์ประกอบใหม่) SideEffect จะส่งเหตุการณ์ที่เกี่ยวข้อง

ความแตกต่างจากการส่งเหตุการณ์โดยตรงใน onClick หรือ onTabSelected คือ SideEffect ถูกกระตุ้นโดยการเปลี่ยนแปลงสถานะจากแหล่งใดๆ — ไม่เพียงแต่การกระทำของผู้ใช้ แต่ยังรวมถึงการเปลี่ยนแปลงเชิงโปรแกรม การกู้คืนสถานะหลังการหมุนหน้าจอ หรือ Deep Links สิ่งนี้ทำให้ SideEffect เป็นกลไกการซิงโครไนซ์สากลที่ไม่ขึ้นกับแหล่งที่มาของการเปลี่ยนแปลง

ตาม Firebase Best Practices (Google, 2025) การส่งเหตุการณ์วิเคราะห์ผ่าน SideEffect ให้ภาพการเดินทางของผู้ใช้ที่สมบูรณ์มากขึ้น เนื่องจากมันจับภาพการเปลี่ยนแปลงสถานะทั้งหมด รวมถึงที่เกิดขึ้นโดยไม่มีการกระทำโดยตรงของผู้ใช้ อย่างไรก็ตาม สิ่งสำคัญคืออย่ามากเกินไป: ทุกเหตุการณ์วิเคราะห์คือคำขอเครือข่าย ดังนั้นสำหรับสถานะที่เปลี่ยนแปลงบ่อย (ตำแหน่งเลื่อน, พิกัดนิ้ว) SideEffect ไม่เหมาะสม — ใช้ debounce หรือส่งเหตุการณ์เฉพาะเมื่อมีการเปลี่ยนแปลงที่มีนัยสำคัญ

kotlin
@Composable
fun ProductScreen(selectedTab: ProductTab, productId: String) {
    val firebaseAnalytics = remember { FirebaseAnalytics.getInstance(LocalContext.current) }
    
    SideEffect {
        val params = Bundle().apply {
            putString(FirebaseAnalytics.Param.CONTENT_TYPE, selectedTab.name)
            putString(FirebaseAnalytics.Param.ITEM_ID, productId)
        }
        firebaseAnalytics.logEvent(        FirebaseAnalytics.Event.VIEW_ITEM, params)
    }
    
    // UI with TabRow and selected tab
}

SideEffect vs LaunchedEffect: ควรใช้อะไรเมื่อใด

การเลือกระหว่าง SideEffect และ LaunchedEffect ขึ้นอยู่กับสองปัจจัย: จำเป็นต้องดำเนินการแบบอะซิงโครนัสหรือไม่และจำเป็นต้องควบคุมแบบคีย์หรือไม่ SideEffect เป็นแบบซิงโครนัสและดำเนินการในการเรียกองค์ประกอบใหม่ทุกครั้ง LaunchedEffect เป็นแบบอะซิงโครนัส (coroutine) และดำเนินการเฉพาะเมื่อคีย์เปลี่ยนแปลง ไม่ใช่ในการเรียกองค์ประกอบใหม่ทุกครั้ง

คุณลักษณะSideEffectLaunchedEffect
การดำเนินการในการเรียกองค์ประกอบใหม่ทุกครั้งเมื่อคีย์เปลี่ยนแปลง
อะซิงโครนัสซิงโครนัสCoroutine
คีย์ไม่มีมี (vararg)
การทำความสะอาดไม่มียกเลิก coroutine อัตโนมัติ
การใช้งานทั่วไปCallback, วิเคราะห์, ซิงโครไนซ์มุมมองโหลด, สมัครสมาชิก Flow, ตัวจับเวลา

ในทางปฏิบัติ 70% ของกรณีการใช้งานเอฟเฟกต์ข้างเคียงถูกครอบคลุมโดย LaunchedEffect (การดำเนินการแบบอะซิงโครนัส, การโหลดข้อมูล), 20% โดย DisposableEffect (ทรัพยากรที่มีการทำความสะอาด) และเพียง 10% โดย SideEffect (การซิงโครไนซ์ callback) SideEffect เป็นเครื่องมือเฉพาะสำหรับช่วงงานที่จำกัด แต่ในงานเหล่านั้นมันเป็นสิ่งที่ขาดไม่ได้

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

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

ข้อผิดพลาดที่สองคือ การคำนวณหนักภายใน SideEffect เนื่องจาก SideEffect ถูกเรียกในการเรียกองค์ประกอบใหม่ทุกครั้ง และการเรียกองค์ประกอบใหม่อาจเกิดขึ้นหลายสิบครั้งต่อวินาที (ระหว่างแอนิเมชัน, การเลื่อน) โค้ดหนักใดๆ ภายใน SideEffect จะทำให้เฟรมดรอป ย้ายการดำเนินการหนักออกจาก composition — ไปยัง coroutine (LaunchedEffect) หรือคำนวณผ่าน derivedStateOf / remember

ข้อผิดพลาดที่สามคือ การพยายามใช้ SideEffect สำหรับโค้ดอะซิงโครนัส SideEffect ไม่ใช่ฟังก์ชัน suspend ดังนั้น delay(), await(), collect() และการดำเนินการ coroutine อื่นๆ ภายในมันจะไม่คอมไพล์ ถ้าคุณต้องการดำเนินการแบบอะซิงโครนัสหลังการเรียกองค์ประกอบใหม่ ให้ใช้ snapshotFlow { ... } ร่วมกับ LaunchedEffect หรือเริ่ม coroutine ผ่าน rememberCoroutineScope

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

SideEffect ถูกดำเนินการในการ composition ครั้งแรกหรือไม่?

ใช่ SideEffect ถูกดำเนินการในทุก composition ที่สำเร็จ รวมถึงครั้งแรก — เมื่อคอมโพเนนต์ปรากฏบนหน้าจอเป็นครั้งแรก สิ่งนี้แตกต่างจาก LaunchedEffect(Unit) ซึ่งถูกดำเนินการครั้งเดียวในการ composition แรก แต่ไม่ถูกดำเนินการในการเรียกองค์ประกอบใหม่ครั้งต่อไป (ถ้าคีย์ไม่เปลี่ยนแปลง)

SideEffect สามารถทำให้เกิดลูปไม่สิ้นสุดได้หรือไม่?

ไม่ SideEffect ถูกดำเนินการหลังจากเฟส composition — การเปลี่ยนแปลงที่ทำภายในมันจะถูกนำไปใช้ในเฟรมถัดไปเท่านั้น ซึ่งป้องกันลูป อย่างไรก็ตาม การเปลี่ยนแปลงสถานะบ่อยครั้งภายใน SideEffect อาจทำให้เกิดการเรียกองค์ประกอบใหม่เป็นลูกโซ่ ลดประสิทธิภาพ เปลี่ยนแปลงสถานะภายใน SideEffect เฉพาะเมื่อจำเป็นจริงๆ

ความแตกต่างระหว่าง SideEffect และ snapshotFlow คืออะไร?

SideEffect ถูกดำเนินการแบบซิงโครนัสในการเรียกองค์ประกอบใหม่ทุกครั้ง snapshotFlow สร้าง Flow จากสถานะ Compose และสามารถใช้กับ collectLatest ใน LaunchedEffect สำหรับการจัดการการเปลี่ยนแปลงแบบรีแอกทีฟ snapshotFlow เหมาะสำหรับกรณีที่คุณต้องตอบสนองต่อการเปลี่ยนแปลงด้วย debounce, filter หรือ distinctUntilChanged — ซึ่งเป็นไปไม่ได้ใน SideEffect แบบซิงโครนัส

จะดีบัก SideEffect ถ้ามันถูกดำเนินการบ่อยเกินไปได้อย่างไร?

ใช้ Android Studio Compose Modifier Debugger หรือเพิ่มการบันทึกด้วยชื่อคอมโพเนนต์และความถี่การเรียก ถ้า SideEffect ถูกดำเนินการบ่อยกว่าที่คาดไว้ ให้ตรวจสอบว่าสถานะของคอมโพเนนต์แม่เปลี่ยนแปลงโดยไม่จำเป็นหรือไม่ การปรับเหมาะสม: แยกส่วนที่เสถียรของ UI ออกเป็นฟังก์ชัน composable แยกต่างหากด้วยคำอธิบายประกอบ unstable เพื่อลดจำนวนการเรียกองค์ประกอบใหม่

สามารถรวม SideEffect กับ DisposableEffect ได้หรือไม่?

ใช่ สามารถใช้ในคอมโพเนนต์เดียวกันเพื่อวัตถุประสงค์ที่แตกต่างกัน DisposableEffect รับผิดชอบการตั้งค่าและการทำความสะอาดทรัพยากร (ครั้งเดียว) ในขณะที่ SideEffect จัดการการซิงโครไนซ์สถานะปัจจุบันกับทรัพยากรนั้นในการเรียกองค์ประกอบใหม่ทุกครั้ง ตัวอย่างทั่วไป: DisposableEffect ลงทะเบียน callback ผ่าน API และ SideEffect อัปเดตข้อมูลที่จับภาพใน callback นั้นทุกครั้งที่มีการเปลี่ยนแปลง

สรุป

  • SideEffect — API เอฟเฟกต์ข้างเคียงสำหรับโค้ดซิงโครนัสที่ถูกดำเนินการหลังจากการเรียกองค์ประกอบใหม่ที่สำเร็จใน Jetpack Compose
  • การซิงโครไนซ์ — กรณีการใช้งานหลัก: ส่งผ่านสถานะ Compose ไปยังระบบภายนอก (Google Maps, WebView, ViewPager, Analytics SDK)
  • Callbacks — SideEffect รับประกันว่าฟังก์ชัน callback ที่จับภาพสถานะปัจจุบันยังคงเป็นปัจจุบันทุกครั้งที่มีการอัปเดต UI
  • ไม่มีลูป — ถูกดำเนินการหลังจากเฟส composition ดังนั้นการเปลี่ยนแปลงสถานะภายใน SideEffect จะไม่ทำให้เกิดการเรียกองค์ประกอบใหม่ไม่สิ้นสุด
  • ข้อจำกัด — ไม่รองรับคีย์, อะซิงโครนัส หรือบล็อกทำความสะอาด; สำหรับงานเหล่านี้ให้ใช้ LaunchedEffect หรือ DisposableEffect
  • ประสิทธิภาพ — หลีกเลี่ยงการคำนวณหนักภายใน SideEffect เนื่องจากมันถูกดำเนินการในการเรียกองค์ประกอบใหม่ทุกครั้ง (สูงสุด 60 ครั้งต่อวินาที)
  • การดีบัก — ควบคุมความถี่การเรียกผ่าน Compose Debugger และปรับเหมาะสมด้วย remember เพื่อกรองการเรียกองค์ประกอบใหม่ที่ไม่จำเป็น

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

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

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

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