ผลข้างเคียงใน Compose

ผลข้างเคียงคือการเปลี่ยนแปลงสถานะของแอปที่เกิดขึ้นนอก ขอบเขตของฟังก์ชันที่ใช้ Compose เนื่องจากวงจรของ Composable และพร็อพเพอร์ตี้ต่างๆ เช่น การประกอบใหม่ที่คาดเดาไม่ได้ การดำเนินการประกอบใหม่ของ Composable ในลำดับที่ต่างกัน หรือการประกอบใหม่ที่ทิ้งได้ Composable ควรไม่มีผลข้างเคียง

อย่างไรก็ตาม บางครั้งก็จำเป็นต้องมีผลข้างเคียง เช่น เพื่อทริกเกอร์เหตุการณ์แบบครั้งเดียว เช่น การแสดงแถบแสดงข้อความหรือการไปยังหน้าจออื่นเมื่อมีเงื่อนไขสถานะหนึ่งๆ ควรเรียกใช้การดำเนินการเหล่านี้จากสภาพแวดล้อมที่ควบคุมได้ซึ่งรับรู้ถึงวงจรของ Composable ในหน้านี้ คุณจะได้เรียนรู้เกี่ยวกับ API ผลข้างเคียงต่างๆ ที่ Jetpack Compose มีให้

กรณีการใช้งานสถานะและเอฟเฟกต์

ตามที่กล่าวไว้ในเอกสารการคิดใน Compose คอมโพสابلไม่ควรมีผลข้างเคียง เมื่อต้องการทำการเปลี่ยนแปลงสถานะของแอป (ตามที่อธิบายไว้ในเอกสารการจัดการสถานะ) คุณควรใช้ Effect API เพื่อให้ระบบดำเนินการกับผลข้างเคียงเหล่านั้นในลักษณะที่คาดการณ์ได้

เนื่องจากเอฟเฟกต์ต่างๆ ที่เป็นไปได้ใน Compose ทำให้ผู้ใช้ ใช้เอฟเฟกต์มากเกินไปได้ง่าย ตรวจสอบว่างานที่คุณทำในนั้นเกี่ยวข้องกับ UI และไม่ทำให้การไหลของข้อมูลแบบทิศทางเดียวหยุดทำงานตามที่อธิบายไว้ในเอกสารประกอบการจัดการสถานะ

LaunchedEffect: เรียกใช้ฟังก์ชันระงับในขอบเขตของ Composable

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

เช่น นี่คือภาพเคลื่อนไหวที่เพิ่มค่าอัลฟ่าโดยมี การหน่วงเวลาที่กำหนดค่าได้

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

ในโค้ดด้านบน ภาพเคลื่อนไหวใช้ฟังก์ชันระงับ delay เพื่อรอตามระยะเวลาที่กำหนด จากนั้นจะเคลื่อนไหวอัลฟ่า เป็น 0 และกลับมาอีกครั้งโดยใช้ animateTo ซึ่งจะทำซ้ำตลอดอายุการใช้งานของ Composable

rememberCoroutineScope: รับขอบเขตที่รับรู้การจัดองค์ประกอบเพื่อเปิดใช้โครูทีนภายนอก Composable

เนื่องจาก LaunchedEffect เป็นฟังก์ชันที่ใช้ร่วมกันได้ จึงใช้ได้เฉพาะภายในฟังก์ชันที่ใช้ร่วมกันได้อื่นๆ หากต้องการเปิดใช้โครูทีนภายนอก Composable แต่กำหนดขอบเขตเพื่อให้ระบบยกเลิกโครูทีนโดยอัตโนมัติเมื่อออกจาก Composition ให้ใช้ rememberCoroutineScope นอกจากนี้ ให้ใช้ rememberCoroutineScope เมื่อใดก็ตามที่คุณต้องการควบคุมวงจรของ โครูทีนอย่างน้อย 1 รายการด้วยตนเอง เช่น ยกเลิกภาพเคลื่อนไหวเมื่อเกิด เหตุการณ์ของผู้ใช้

rememberCoroutineScope เป็นฟังก์ชันที่สามารถคอมโพสได้ซึ่งจะแสดงผล CoroutineScope ที่เชื่อมโยงกับจุดของ Composition ที่มีการเรียกใช้ ระบบจะยกเลิกขอบเขตเมื่อการเรียกใช้ออกจาก Composition

จากตัวอย่างก่อนหน้า คุณสามารถใช้โค้ดนี้เพื่อแสดง Snackbar เมื่อผู้ใช้แตะ Button ได้

@Composable
fun MoviesScreen(snackbarHostState: SnackbarHostState) {

    // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        }
    ) { contentPadding ->
        Column(Modifier.padding(contentPadding)) {
            Button(
                onClick = {
                    // Create a new coroutine in the event handler to show a snackbar
                    scope.launch {
                        snackbarHostState.showSnackbar("Something happened!")
                    }
                }
            ) {
                Text("Press me")
            }
        }
    }
}

rememberUpdatedState: อ้างอิงค่าในเอฟเฟกต์ที่ไม่ควรรีสตาร์ทหากค่ามีการเปลี่ยนแปลง

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

ตัวอย่างเช่น สมมติว่าแอปของคุณมี LandingScreen ที่หายไปหลังจากผ่านไประยะหนึ่ง แม้ว่าจะมีการจัดองค์ประกอบ LandingScreen ใหม่ แต่เอฟเฟกต์ที่รอสักครู่ และแจ้งว่าเวลาผ่านไปแล้วไม่ควรเริ่มต้นใหม่

@Composable
fun LandingScreen(onTimeout: () -> Unit) {

    // This will always refer to the latest onTimeout function that
    // LandingScreen was recomposed with
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    // Create an effect that matches the lifecycle of LandingScreen.
    // If LandingScreen recomposes, the delay shouldn't start again.
    LaunchedEffect(true) {
        delay(SplashWaitTimeMillis)
        currentOnTimeout()
    }

    /* Landing screen content */
}

หากต้องการสร้างเอฟเฟกต์ที่ตรงกับวงจรการใช้งานของเว็บไซต์ที่เรียกใช้ ให้ส่งค่าคงที่ที่ไม่เปลี่ยนแปลง เช่น Unit หรือ true เป็นพารามิเตอร์ ใน โค้ดด้านบน เราใช้ LaunchedEffect(true) หากต้องการให้ onTimeout Lambda มีค่าล่าสุดที่ LandingScreen สร้างขึ้นใหม่เสมอ onTimeout จะต้องอยู่ในฟังก์ชัน rememberUpdatedState ควรใช้ State, currentOnTimeout ที่ส่งคืนในโค้ดใน เอฟเฟกต์

DisposableEffect: เอฟเฟกต์ที่ต้องมีการล้างข้อมูล

สำหรับผลข้างเคียงที่ต้องล้างข้อมูลหลังจากที่คีย์มีการเปลี่ยนแปลงหรือหาก Composable ออกจาก Composition ให้ใช้ DisposableEffect หากคีย์ DisposableEffect เปลี่ยนไป Composable จะต้องทิ้ง (ล้างข้อมูล) เอฟเฟกต์ปัจจุบัน และรีเซ็ตโดยการเรียกใช้เอฟเฟกต์อีกครั้ง

ตัวอย่างเช่น คุณอาจต้องการส่งเหตุการณ์ Analytics โดยอิงตามLifecycle เหตุการณ์ โดยใช้ LifecycleObserver หากต้องการฟังเหตุการณ์เหล่านั้นใน Compose ให้ใช้ DisposableEffect เพื่อลงทะเบียนและ ยกเลิกการลงทะเบียน Observer เมื่อจำเป็น

@Composable
fun HomeScreen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit, // Send the 'started' analytics event
    onStop: () -> Unit // Send the 'stopped' analytics event
) {
    // Safely update the current lambdas when a new one is provided
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    // If `lifecycleOwner` changes, dispose and reset the effect
    DisposableEffect(lifecycleOwner) {
        // Create an observer that triggers our remembered callbacks
        // for sending analytics events
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                currentOnStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                currentOnStop()
            }
        }

        // Add the observer to the lifecycle
        lifecycleOwner.lifecycle.addObserver(observer)

        // When the effect leaves the Composition, remove the observer
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    /* Home screen content */
}

ในโค้ดด้านบน เอฟเฟกต์จะเพิ่ม observer ไปยัง lifecycleOwner หาก lifecycleOwner เปลี่ยนแปลง ระบบจะทิ้งเอฟเฟกต์และ รีสตาร์ทด้วย lifecycleOwner ใหม่

DisposableEffect ต้องมีonDisposeเป็นข้อความสุดท้าย ในบล็อกโค้ด มิฉะนั้น IDE จะแสดงข้อผิดพลาดขณะคอมไพล์

SideEffect: เผยแพร่สถานะ Compose ไปยังโค้ดที่ไม่ใช่ Compose

หากต้องการแชร์สถานะ Compose กับออบเจ็กต์ที่ไม่ได้จัดการโดย Compose ให้ใช้ Composable SideEffect การใช้ SideEffect จะรับประกันว่าเอฟเฟกต์จะทำงานหลังจากการจัดองค์ประกอบใหม่ที่สำเร็จทุกครั้ง ในทางกลับกัน การ ใช้เอฟเฟกต์ก่อนที่จะรับประกันการประกอบใหม่ที่สำเร็จนั้นไม่ถูกต้อง ซึ่งเป็นกรณีที่ เขียนเอฟเฟกต์โดยตรงใน Composable

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

@Composable
fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember {
        FirebaseAnalytics()
    }

    // On every successful composition, update FirebaseAnalytics with
    // the userType from the current User, ensuring that future analytics
    // events have this metadata attached
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    return analytics
}

produceState: แปลงสถานะที่ไม่ใช่ Compose เป็นสถานะ Compose

produceState เปิดใช้โครูทีนที่กำหนดขอบเขตไว้ใน Composition ซึ่งสามารถส่งค่าไปยัง State ที่ส่งคืนได้ ใช้เพื่อ แปลงสถานะที่ไม่ใช่ Compose เป็นสถานะ Compose เช่น การนำสถานะที่ขับเคลื่อนด้วยการสมัครใช้บริการภายนอก เช่น Flow, LiveData หรือ RxJava มาไว้ใน Composition

ระบบจะเปิดตัวโปรดิวเซอร์เมื่อ produceState เข้าสู่การเรียบเรียง และจะยกเลิกเมื่อออกจากเรียบเรียง State ที่แสดงผลจะรวมกัน การตั้งค่าเดียวกันจะไม่ทริกเกอร์การจัดองค์ประกอบใหม่

แม้ว่า produceState จะสร้างโครูทีน แต่ก็ยังใช้เพื่อสังเกตแหล่งข้อมูลที่ไม่ระงับได้ด้วย หากต้องการยกเลิกการติดตามแหล่งข้อมูลนั้น ให้ใช้ฟังก์ชัน awaitDispose

ตัวอย่างต่อไปนี้แสดงวิธีใช้ produceState เพื่อโหลดรูปภาพจากเครือข่าย ฟังก์ชันที่ใช้ร่วมกันได้ loadNetworkImage จะแสดงผล State ที่ใช้ในฟังก์ชันที่ใช้ร่วมกันได้อื่นๆ ได้

@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository = ImageRepository()
): State<Result<Image>> {
    // Creates a State<T> with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new inputs.
    return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {
        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}

derivedStateOf: แปลงออบเจ็กต์สถานะอย่างน้อย 1 รายการเป็นสถานะอื่น

ใน Compose การประกอบใหม่จะเกิดขึ้น ทุกครั้งที่ออบเจ็กต์สถานะที่สังเกตได้หรืออินพุตที่ประกอบได้มีการเปลี่ยนแปลง ออบเจ็กต์สถานะ หรืออินพุตอาจเปลี่ยนแปลงบ่อยกว่าที่ UI จำเป็นต้องอัปเดต ซึ่งนำไปสู่การจัดองค์ประกอบที่ไม่จำเป็น

คุณควรใช้ฟังก์ชัน derivedStateOf เมื่ออินพุตไปยัง Composable เปลี่ยนแปลงบ่อยกว่าที่คุณต้องการ ให้ทำการ Recompose กรณีนี้มักเกิดขึ้นเมื่อมีการเปลี่ยนแปลงบางอย่างบ่อยๆ เช่น ตำแหน่งการเลื่อน แต่ Composable จำเป็นต้องตอบสนองต่อการเปลี่ยนแปลงนั้นเมื่อข้าม เกณฑ์ที่กำหนดเท่านั้น derivedStateOf จะสร้างออบเจ็กต์สถานะ Compose ใหม่ที่คุณ สังเกตได้ว่าจะอัปเดตเฉพาะเท่าที่จำเป็นเท่านั้น ด้วยวิธีนี้ จึงทําหน้าที่ คล้ายกับตัวดำเนินการ distinctUntilChanged() ของ Kotlin Flows

การใช้งานที่ถูกต้อง

ข้อมูลโค้ดต่อไปนี้แสดงกรณีการใช้งานที่เหมาะสมสำหรับ derivedStateOf

@Composable
// When the messages parameter changes, the MessageList
// composable recomposes. derivedStateOf does not
// affect this recomposition.
fun MessageList(messages: List<Message>) {
    Box {
        val listState = rememberLazyListState()

        LazyColumn(state = listState) {
            // ...
        }

        // Show the button if the first visible item is past
        // the first item. We use a remembered derived state to
        // minimize unnecessary compositions
        val showButton by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex > 0
            }
        }

        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()
        }
    }
}

ในข้อมูลโค้ดนี้ firstVisibleItemIndex จะเปลี่ยนทุกครั้งที่รายการแรกที่มองเห็นได้ มีการเปลี่ยนแปลง เมื่อเลื่อน ค่าจะกลายเป็น 0, 1, 2, 3, 4, 5 ฯลฯ อย่างไรก็ตาม การเขียนคอมโพสใหม่จะเกิดขึ้นก็ต่อเมื่อค่ามากกว่า 0 เท่านั้น ความถี่ในการอัปเดตที่ไม่ตรงกันนี้หมายความว่ากรณีการใช้งานนี้เหมาะสำหรับ derivedStateOf

การใช้งานไม่ถูกต้อง

ข้อผิดพลาดที่พบบ่อยคือการคิดว่าเมื่อรวมออบเจ็กต์สถานะ Compose 2 รายการ คุณควรใช้ derivedStateOf เนื่องจากคุณกำลัง "สร้างสถานะ" อย่างไรก็ตาม การดำเนินการนี้ เป็นเพียงค่าใช้จ่ายเพิ่มเติมและไม่จำเป็น ดังที่แสดงในตัวอย่างโค้ดต่อไปนี้

// DO NOT USE. Incorrect usage of derivedStateOf.
var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }

val fullNameBad by remember { derivedStateOf { "$firstName $lastName" } } // This is bad!!!
val fullNameCorrect = "$firstName $lastName" // This is correct

ในข้อมูลโค้ดนี้ fullName ต้องอัปเดตบ่อยเท่ากับ firstName และ lastName ดังนั้นจึงไม่มีการจัดองค์ประกอบใหม่มากเกินไป และไม่จำเป็นต้องใช้ derivedStateOf

snapshotFlow: แปลงสถานะของ Compose เป็นโฟลว์

ใช้ snapshotFlow เพื่อแปลงออบเจ็กต์ State<T> เป็น Flow แบบเย็น snapshotFlow จะเรียกใช้บล็อกเมื่อรวบรวมและส่งผลลัพธ์ของออบเจ็กต์ State ที่อ่านในบล็อก เมื่อStateออบเจ็กต์ ใดออบเจ็กต์หนึ่งที่อ่านภายในบล็อก snapshotFlow มีการเปลี่ยนแปลง Flow จะปล่อยค่าใหม่ ไปยังตัวรวบรวมหากค่าใหม่ไม่เท่ากับ ค่าที่ปล่อยก่อนหน้า (ลักษณะการทำงานนี้คล้ายกับของ Flow.distinctUntilChanged)

ตัวอย่างต่อไปนี้แสดงผลข้างเคียงที่บันทึกเมื่อผู้ใช้เลื่อน ผ่านรายการแรกในรายการไปยัง Analytics

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}

ในโค้ดด้านบน listState.firstVisibleItemIndex จะแปลงเป็น Flow ที่ ใช้ประโยชน์จากพลังของตัวดำเนินการของ Flow ได้

การรีสตาร์ทเอฟเฟกต์

เอฟเฟกต์บางอย่างใน Compose เช่น LaunchedEffect, produceState หรือ DisposableEffect จะใช้จำนวนอาร์กิวเมนต์และคีย์ที่แตกต่างกัน ซึ่งใช้เพื่อ ยกเลิกเอฟเฟกต์ที่กำลังทำงานและเริ่มเอฟเฟกต์ใหม่ด้วยคีย์ใหม่

รูปแบบทั่วไปของ API เหล่านี้มีดังนี้

EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }

เนื่องจากลักษณะการทำงานนี้มีความซับซ้อน ปัญหาจึงอาจเกิดขึ้นได้หากพารามิเตอร์ ที่ใช้เพื่อรีสตาร์ทเอฟเฟกต์ไม่ถูกต้อง

  • การรีสตาร์ทเอฟเฟกต์น้อยกว่าที่ควรจะเป็นอาจทำให้เกิดข้อบกพร่องในแอป
  • การรีสตาร์ทเอฟเฟกต์บ่อยเกินไปอาจไม่มีประสิทธิภาพ

โดยทั่วไปแล้ว ตัวแปรที่เปลี่ยนแปลงได้และเปลี่ยนแปลงไม่ได้ซึ่งใช้ในบล็อกเอฟเฟกต์ของโค้ดควรเพิ่มเป็นพารามิเตอร์ไปยังฟังก์ชันที่ใช้คอมโพสเอฟเฟกต์ นอกเหนือจากพารามิเตอร์เหล่านั้น คุณยังเพิ่มพารามิเตอร์อื่นๆ เพื่อบังคับให้เอฟเฟกต์รีสตาร์ทได้ด้วย หากการเปลี่ยนแปลงตัวแปรไม่ควรทำให้เอฟเฟกต์รีสตาร์ท คุณควรใส่ตัวแปรไว้ใน rememberUpdatedState หากตัวแปรไม่เคยเปลี่ยนแปลงเนื่องจากอยู่ใน remember ที่ไม่มีคีย์ คุณไม่จำเป็นต้องส่งตัวแปรเป็นคีย์ไปยังเอฟเฟกต์

ในDisposableEffectโค้ดที่แสดงด้านบน เอฟเฟกต์จะใช้เป็นพารามิเตอร์ lifecycleOwnerที่ใช้ในบล็อก เนื่องจากหากมีการเปลี่ยนแปลงใดๆ กับพารามิเตอร์เหล่านั้น จะทำให้เอฟเฟกต์รีสตาร์ท

@Composable
fun HomeScreen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit, // Send the 'started' analytics event
    onStop: () -> Unit // Send the 'stopped' analytics event
) {
    // These values never change in Composition
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            /* ... */
        }

        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

ไม่จำเป็นต้องใช้ currentOnStart และ currentOnStop เป็นคีย์ DisposableEffect เนื่องจากค่าของคีย์ดังกล่าวจะไม่เปลี่ยนแปลงใน Composition เนื่องจากมีการใช้ rememberUpdatedState หากคุณไม่ส่ง lifecycleOwner เป็นพารามิเตอร์และ มีการเปลี่ยนแปลง HomeScreen จะสร้างใหม่ แต่ DisposableEffect จะไม่ถูกทิ้ง และรีสตาร์ท ซึ่งจะทำให้เกิดปัญหาเนื่องจากมีการใช้ lifecycleOwner ที่ไม่ถูกต้อง นับจากนั้นเป็นต้นไป

ค่าคงที่เป็นคีย์

คุณสามารถใช้ค่าคงที่ เช่น true เป็นคีย์เอฟเฟกต์เพื่อติดตามวงจรของเว็บไซต์ที่เรียกใช้ มีกรณีการใช้งานที่ถูกต้องสำหรับ ฟีเจอร์นี้ เช่น LaunchedEffectตัวอย่างที่แสดงด้านบน อย่างไรก็ตาม ก่อนที่จะดำเนินการดังกล่าว โปรดคิดให้รอบคอบและตรวจสอบว่าคุณต้องการดำเนินการดังกล่าวจริง