Jetpack Compose 核心概念(二)

7. Compose 的渲染

7.1 Compose 渲染过程

对于任意一个 composable 的渲染主要分为三个阶段:

  1. Composition,在这一阶段决定哪些 composable 会被渲染并显示出来。
  2. Layout,在这一阶段会进行测量和布局,也就是确认 composable 的大小和摆放的位置。
  3. Drawing,在这一阶段主要是完成绘制工作,将要展示的 composable 绘制到 canvas 上。
image

Composition

Composition 分为 initial composition 和 recomposition 两个过程。初次加载 Compose 结构树的过程主要是决定哪些 composable 会被显示出来,以及完成 composable 与 state 对象的绑定工作。

Recomposition 是当 UI 已经显示出来后,由于 composable 持有的 state 在与用户交互过程中,发生了变化,而引起 UI 局部刷新的过程。这个局部刷新主要是以持有 state 状态发生变化的 composabe 为根,根据子 composable 的输入是否改变来向下递归的进行 UI 的刷新。

@Composable
fun CompositionExample() {
    Log.d("composition", "CompositionExample")
    var inputState by remember { mutableStateOf("") }

    Column {
        HomeScreen(inputState) {
            inputState = it
        }

        HomeBottom()
    }
}

@Composable
fun HomeBottom() {
    Log.d("composition", "HomeBottom")
    Text(text = "This is the bottom")
}

@Composable
fun HomeScreen(value: String, textChanged: (String) -> Unit) {
    Log.d("composition", "HomeScreen")
    TextField(value = value, onValueChange = { textChanged(it) })
}

上面的代码在完成 initial composition 的渲染后,如果在 TextFiled 输入框中输入新的内容,会引起 state 状态的变化,进而引发 recomposition 的刷新操作。

当在进行 recomposition 的刷新时,首先,直接持有 state 状态对象的 composable 会进行刷新,打印出 CompositionExample 日志;当 CompositionExample 在刷新的过程中,执行到 HomeScreen composable 时,发现其输入参数发生了变化,会递归到 HomeScreen 中进行刷新,此时,HomeScreen 日志会被打印;HomeScreen 执行完成后,执行到 HomeBottom composable 时,由于其没有输入参数,意味着此 composable 的输入没有发生改变,所以,不会执行 HomeBottom composable 及其子 composable。(这里 HomeScreen 和 HomeBottom 不一定是顺序调用,两者可能是并发同时在不同的线程被调用)

Layout

Layout 包含了两个步骤:测量和布局,也就是测量 composable 的大小及确定摆放的位置。

Layout 阶段主要包含三个步骤:

  1. Measure children(测量子 composable 节点的大小)
  2. Measure own size(测量自己的大小)
  3. Place children(为子 composable 指定摆放位置)

这三个步骤使用的是深度优先的递归策略执行的,如下图所示:

image

以 Box1 的 layout 过程为例:

  1. Box1 先测量其所有子 composable 的大小;
  2. Box1 测量自己的大小;
  3. Box1 为其所有子 composable 指定其摆放的位置。

MeasureScope.measure 主要是负责测量的操作;MeasureScope.layout 主要负责指定摆放位置的操作。

测量(measure)和定位(place)的独立性:

  1. 测量和定位两个步骤的操作是相互独立的;
  2. 如果在测量过程中,读取了 state 状态,由于测量通常都发生在自定义 Layout 过程中,而测量后,紧接着就是定位的操作,所以,当测量过程中读取的 state 状态发生变化时,会同时触发测量和定位两个操作。而当定位过程中,读取了 state 状态,由于定位可以直接在 composable 的 modifier 进行配置(如:Modifier.offset{....}),当其内部引用的 state 状态发生变化时,只会执行定位的操作,而不会触发测量的执行。
var offsetX by remember { mutableStateOf(8.dp) }
Text(
    text = "Hello",
    modifier = Modifier.offset {
        // 当 offsetX 状态发生变化时,只会触发定位逻辑 (layout)的执行
        IntOffset(offsetX.roundToPx(), 0)
    }
)

Drawing

绘制通常使用两种方式实现,一种方式是直接创建 Canvas 对象,并调用 drawXXX 相关方法来进行自定义绘制;另一种方式是调用 Modifer.drawBehind{...} 或者 Modifier.drawContent{...} 来进行自定义绘制。

image

在进行自定义绘制过程中,如果引用了 state 状态对象,当 state 状态对象发生变化时,只会触发绘制阶段的逻辑,而不会触发测量或者定位阶段的逻辑。

var color by remember { mutableStateOf(Color.Red) }
Canvas(modifier = modifier) {
    // 当 color 状态发生变化时,只会导致 drawRect 方法的再次执行
    drawRect(color)
}

7.2 State 与 Layout 阶段的关系

image
  1. 如果 Composable function 或者 composable lambda 绑定的 state 发生了变化后,会触发 composition 来刷新 UI;在 composition 过程中,如果内容发生了变化,会执行对应 composable 的 layout 操作;在 layout 的过程中,如果 composable 的大小或者位置发生了变化,则会执行对应 composable 的 drawing 操作。
  2. 如果自定义 Layout 中或者 Modifier.offset 绑定的 state 发生了变化后,会触发 Layout 的操作对相应的 composable 进行测量和定位的操作;如果测量或者定位过程中,对应的大小或者位置发生了变化,则会触发 Drawing 的操作。
  3. 如果自定义绘制 CanvasModifier.drawBehind 或者 Modifer.drawContent 所绑定的 state 对象发生了变化,则会触发对应 composable drawing 阶段的操作。

8. Modifier 、作用及其执行顺序

典型 Modifier 作为参数的用法及说明

@Composable
fun show(modifier: Modifier = Modifier) {
    Box(modifier.background(Color.Red)) {

    }
}

基本上来说,一般在定义一个 composable 的时候,都会将 modifier 作为参数传入,且会给一个默认值 Modifier 对象。= 左右两边的 Modifier 分别代表什么呢?

= 左边的 Modifier 实际上是一个 Modifier 接口,代表当前 modifier 的类型。

= 右边的 Modifier 实际上是一个 Modifier 接口的默认实现类,该默认实现其实什么也没有做,只是一个空的实现。

这种写的好处是:

每一个 Composable 都能接收到外部 Composable 对其的约束。
当外部对当前 Composable 没有任何要求时,会使用默认的 Modifier 对象,不会改变任何预期。

Modifier 的执行顺序

多个 Modifier 链式调用在编译过程中会被编译成一个嵌套的关系,其嵌套的原则是链头部分在嵌套的最外层,链尾部分在嵌套结构的最里层。

show(modifier = Modifier.padding(20.dp).size(80.dp))

[图片上传失败...(image-f9d7e-1641956813507)]

当编译过后的执行过程是从嵌套的最内层依次往外层进行执行的,也就是从链的最右边为执行的起点,依次执行,直到链最左边的调用被执行完为止。

如上面 show 函数中的 modifier 的执行过程是:

先执行 .size(80.dp),再执行 .paading(20.dp),其执行结果为:

image

针对 size、width 和 height 等 布局相关的配置,如果同样的配置被重复配置,且值是不同的,则前面执行的配置会被后面的配置所覆盖。

show(modifier = Modifier.padding(20.dp).size(80.dp).size(10.dp))

由于先执行了 .size(10.dp) 后,再执行的 .size(80.dp),后面的将前面的配置覆盖了,所以,默认情况下,只有 .size(80.dp) 的配置才会被生效。

针对 size、width 和 height 等的配置,可以通过 requiredXXX 来改变其默认的执行结果

show(
    modifier = Modifier
        .padding(20.dp)
        .background(Color.Green)
        .size(80.dp)
        .requiredSize(10.dp)
        .background(Color.Red)
)

上面代码的执行结果为:

image

从上图可以看出,使用了 requiredSize 后,其配置显示出来了,但他影响的只是在它前面执行(也就是链后)的配置。在其它后面执行的 .size(80.dp) (背景为绿色部分)还是正常显示,并没有任何影响。

针对 size、width 和 height 等的配置,如果 requiredSize 的值比后面执行的 Size 的值要大,也会被 Size 给约束,如果有 padding 值的话,会变成 requiredSize 的一部分

image

9. Jetpack Compose 架构,各层的作用及如何添加依赖

9.1 Jetpack Compose 五层架构

依赖库包各层作用说明:

image

依赖关系图:

image

为什么 Button 会在 Material 库里面,而不是在 foundation 库里面?

因为在 Compose 中 Button 的组件的组成是非常灵活的,里面需要指定不同的组件及排列方式(如:Text()、Icon()、Column() 等等)。我们所使用的 Button 之所以放在 Material 包里面,是因为在 Material 库中指定了默认的排列顺序 Row()。

同一层中的多个包又是什么关系呢?

一般来说,我们只需要引入同一层中同名的库包,就会将其所属的其他包一起加入进来。如:androidx.compose.ui:ui:xxx 包就包含了 androidx.compose.ui:ui-text:xxxandroidx.compose.ui:ui-graphics:xxxandroidx.compose.ui:ui-util:xxx 等库包。

例外情况:

androidx.compose.ui:ui:xxx 不包含 androidx.compose.ui:ui-tooling:xxx

androidx.compose.material:material:xxx 不包含 androidx.compose.material:material-icons-extended:xxx

9.2 五层架构的好处

  1. 灵活控制。层级越高的组件,使用起来更加简单,但相对限制更多;层级越低的组件,可扩展性超高,但使用起来也相对复杂。使用者可以根据需求灵活选择使用哪一层级的组件。
  2. 自定义简单。自定义高级别组件的时候,可以非常容易的通过组合低级别的组件来完成自定义的工作。比如:Material 层级的 Button 按钮就是通过组合 Material、Foundatation、Runtime 层级的组件来完成自定义功能的。
image

10. CompositionLocal

CompositionLocal 主要是为了解决 Composable 树结构中,多个底层分支依赖上层某个数据时,需要将对应的值通过函数参数不断向下传递的问题。

使用 CompositionLocal 的流程

  1. 创建 CompositionLocal 对象:通过 staticCompositionLocalOf 或者 compositionLocalOf 两种方式来创建该对象。

1.1 staticCompositionLocalOf

val ColorCompositionLocal = staticCompositionLocalOf<Color> {
    error("No Color provided")
}

1.2 compositionLocalOf

val ColorCompositionLocal = compositionLocalOf<Color> {
    error("No Color provided")
}
  1. 通过 CompositionLocalProvider 指定 CompositionLocal 的作用范围并绑定需要共享的带状态的对象或值
CompositionLocalProvider(LocalActivateUser provides user) {
    UserProfile()
}

这里首先通过 CompositionLocalProvider 指定了 CompositionLocal 的作用范围为 UserProfile() 及其所有子 composable 函数。同时,绑定了 user 对象(带状态)作为共享的值来被 UserProfile() 及其所有子 composable 函数调用。

  1. 在对应的 Composable 函数中调用 CompositionLocal 共享的带状态的值
@Composable
fun UserProfile() {
    Column {
        Text(text = LocalActivateUser.current.name)
    }
}

Note:CompositionLocal 对象的命名一般以 Local 开头。

staticCompositionLocalOf 和 compositionLocalOf 的区别

下面的图是一个使用 CompositionLocal 的例子,点击 click 按钮后,会更新带状态的 Color 的值。分别使用 staticCompositionLocalOf 或者 compositionLocalOf 对象来看看带状态的 Color 值变化后,两者的表现有什么区别。

image
  1. 使用 staticCompositionLocalOf
image

当带状态的 Color 值发生变化后,其被包含的所有 composable function 都会触发 recompose 操作。

  1. 使用 compositionLocalOf

[图片上传失败...(image-e0a720-1641956813508)]

当带状态的 Color 值发生变化后,只有直接引用了 Color 值的 Composable function 才会触发 recompose 操作。

var LocalColorComposition = compositionLocalOf<Color> { error("No color provided") }

var stateColor by mutableStateOf(Color.LightGray)

@Composable
fun CompositionLocalAndStaticCompositionLocal() {
    Column {
        Button(onClick = {
            stateColor = if (stateColor == Color.LightGray) Color.Red else Color.LightGray
        }) {
            Text(text = "Update stateColor")
        }

        CompositionLocalProvider(LocalColorComposition provides stateColor) {
//            CoverCompossables()
            CoverCompossables1()
        }
    }
}

@Composable
fun CoverCompossables1() {
    outsideCount++
    MyBox(color = Color.Green, count = outsideCount) {
        centerCount++
        MyBox(color = LocalColorComposition.current, count = centerCount) {
            insideCount++
            MyBox(color = Color.White, count = insideCount) {
            }
        }
    }
}

@Composable
fun MyBox(color: Color, count: Int, content: @Composable BoxScope.() -> Unit) {
    Column(Modifier.background(color)) {
        Text(text = "current value: $count")
        Box(
            modifier = Modifier
                .padding(16.dp)
                .fillMaxSize(),
            content = content
        )
    }
}

Note:

如果这里没有抽取 MyBox Composable 函数,而是直接以层级形式直接展开的话,上面的特性会失效。无论使用 CompositionLocalOf 还是 StaticCompositionLocalOf,结果都会全部刷新。

未抽取 MyBox Composable 函数的代码:

var LocalColorComposition = compositionLocalOf<Color> { error("No color provided") }

var stateColor by mutableStateOf(Color.LightGray)

@Composable
fun CompositionLocalAndStaticCompositionLocal() {
    Column {
        Button(onClick = {
            stateColor = if (stateColor == Color.LightGray) Color.Red else Color.LightGray
        }) {
            Text(text = "Update stateColor")
        }

        CompositionLocalProvider(LocalColorComposition provides stateColor) {
            CoverCompossables()
//            CoverCompossables1()
        }
    }
}

@Composable
fun CoverCompossables() {
    outsideCount++
    Column(
        Modifier
            .size(1000.dp)
            .background(Color.Green)
    ) {
        Log.d("TAG", "outside")
        Text(text = "current value: $outsideCount")
        Box(
            Modifier
                .padding(16.dp)
                .fillMaxSize(), contentAlignment = Alignment.Center
        ) {
            centerCount++
            Column(
                Modifier
                    .size(800.dp)
                    .background(LocalColorComposition.current)
            ) {
                Log.d("TAG", "center")
                Text(text = "current value: $centerCount")
                Box(
                    Modifier
                        .padding(16.dp)
                        .fillMaxSize(), contentAlignment = Alignment.Center
                ) {
                    insideCount++
                    Column(
                        Modifier
                            .size(600.dp)
                            .background(Color.White)
                    ) {
                        Log.d("TAG", "inside")
                        Text(text = "current value: $insideCount")
                    }
                }
            }
        }
    }
}

作用

解决前的数据传递链路图:

image

解决 Composable 树结构中,多个底层分支依赖上层某个数据时,需要将对应的值通过函数参数不断向下传递的问题。

解决后的数据传递链路图:

image

与全局静态变量的区别

通过 CompositionLocal 共享的值,只能在共享该值的结点及其子结点才能使用。其它地方使用会抛异常。

CompositionLocalProvider 的实现

@Composable
@OptIn(InternalComposeApi::class)
fun CompositionLocalProvider(
    vararg values: ProvidedValue<*>, 
    content: @Composable () -> Unit) {
    currentComposer.startProviders(values)
    content()
    currentComposer.endProviders()
}

可以看到 CompositionLocalProvider 在 content 执行之前开始生效,而在 content 执行之后就被释放了。同时,可以看到,CompositionLocalProvider 接收多个对象或值的共享。

11. Migration(迁移)

11.1 如何获取 xml 资源文件的值

  • dimensionResource(id) -> dimens.xml
  • stringResource(id) -> strings.xml
  • XxxResource(id) -> xxx.xml

11.2 Livedata 在 composable 中如何使用

使用 LiveData 的扩展函数 observeAsState 将其转换为 Composable 中的 State<T> 对象。

@Composable
fun PlantDetailDescription(plantDetailViewModel: PlantDetailViewModel) {
    // Observes values coming from the VM's LiveData<Plant> field
    val plant by plantDetailViewModel.plant.observeAsState()

    // If plant is not null, display the content
    plant?.let {
        PlantDetailContent(it)
    }
}

Note:由于 LiveData 可以发送 null 值,在使用的地方需要判空。

11.3 Compose 中无法显示 HTML 格式的文本

使用 AndroidView 来使用传统的 View System 中的控件并将其显示在 Compose 中。在 AndroidView 中有两个函数类型的参数,一个 factory 参数表示在此创建传统 View System 中的控件,当构建完成后,会回调到 update 函数,并当 factory 中创建的控件当作函数参数传入,此时,就可以对该控件进行设值等操作了。

同时,在 update 回调中引用的外部的 state 变量(如下面的 htmlDescription 值是一个 mutableStateOf 状态对象)变化后,update 会重新被调用。

@Composable
fun androidViewDemo() {
    var htmlDescription by remember {
        mutableStateOf(
            HtmlCompat.fromHtml(
                "HTML<br><br>description",
                HtmlCompat.FROM_HTML_MODE_COMPACT
            )
        )
    }

    Column {
        AndroidView(factory = { content ->
            TextView(content)
        }, update = {
            it.text = htmlDescription
        })

        Button(onClick = {
            htmlDescription =
                HtmlCompat.fromHtml("HTML<br><br>update", HtmlCompat.FROM_HTML_MODE_COMPACT)
        }) {
            Text(text = "更改 text 的显示")
        }
    }
}

Note:在 AndroidView 的 update 回调中,引用的任何 State 状态对象,只要状态对象发生变化后,都会引起 update 回调方法再次执行,类似于 reComposition。

11.4 如何在 Compose 中使用传统 View System 中的 Theme

如果想要在 Compose 中使用传统 View System 中的 Theme,需要使用 compose-theme-adapter 库,该库可以自动将 style 文件中的主题转换成 composable 类型的主题,并生成以 MdcTheme 固定名称 composable 主题。

@Composable
fun MdcTheme(
    context: Context = AmbientContext.current,
    readColors: Boolean = true,
    readTypography: Boolean = true,
    readShapes: Boolean = true,
    setTextColors: Boolean = false,
    content: @Composable () -> Unit
) {
    val key = context.theme.key ?: context.theme

    val themeParams = remember(key) {
        createMdcTheme(
            context = context,
            readColors = readColors,
            readTypography = readTypography,
            readShapes = readShapes,
            setTextColors = setTextColors
        )
    }

    MaterialTheme(
        colors = themeParams.colors ?: MaterialTheme.colors,
        typography = themeParams.typography ?: MaterialTheme.typography,
        shapes = themeParams.shapes ?: MaterialTheme.shapes,
        content = content
    )
}

11.5 如何在传统的 View System 中使用 Compose

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="hello world" />

    <androidx.compose.ui.platform.ComposeView
        android:id="@+id/acv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tv" />

</RelativeLayout>
findViewById<ComposeView>(R.id.acv).setContent { 
            Text(text = "ComposeView")
        }

11.6 如何在 Compose 中使用传统的 View

动态创建传统 View:

setContent {
    Column {
        Text(text = "top")
        AndroidView(factory = {
            View(it).apply {
                setBackgroundColor(android.graphics.Color.GRAY)
            }
        }, Modifier.size(30.dp)) {
            // update
        }
        Text(text = "bottom")
    }
}

factory 的作用:用于创建由传统 View System 所构建的布局。只会被执行一次。

update 的作用:用于界面每次 Recompose 的时候刷新传统 View System 所构建的布局。会拿到一个在 factory 过程中生成的 View 引用对象,来进行操作。

image

11.7 Compose 中内部数据与 Compose 外部数据的交互

Compose 内部使用外部非 State 数据

  1. LiveData 数据更新触发 Recompose(Compose 使用外部数据)

// 外部数据
val result = MutableLiveData(1)

setContent {
    // Compose 内部
    val num = result.observeAsState()

    Text(text = "$num")
}
  1. 协程 Flow 发送数据触发 Recompose(Compose 使用外部数据)
// 外部数据
val flowOjb = flow { emit(1) }
setContent {
    // 内部数据
    val num = flowOjb.collectAsState(initial = 0)
    Text(text = "$num")
}

Compose 外部实现使用 Compose 内部数据

在 Compose 的内部数据的主要表现形式是:State<T>,这个对象是无法转换成其它对象(如:LiveData)的,所以,外部实现是无法使用 Compose 中的数据的。

解决方法:如果外部数据需要得到 Compose 内部数据的话,在一开始设计的时候,就需要将该数据结构定义在外部(如:LiveData)。

12. Intrinsic 固有特性测量

固有特性测量的本质就是父组件可在正式测量布局前预先获取到每个子组件宽高信息后通过计算来确定自身的固定宽度或高度,从而间接影响到其中包含的部分子组件布局信息。

也就是说子组件可以根据自身宽高信息来确定父组件的宽度或高度,从而影响其他子组件布局。

@Composable
fun TwoTexts(modifier: Modifier = Modifier, text1: String, text2: String) {
    Row(modifier = modifier.height(IntrinsicSize.Min)) { // I'm here
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(start = 4.dp)
                .wrapContentWidth(Alignment.Start),
            text = text1
        )

        Divider(color = Color.Black, modifier = Modifier.fillMaxHeight().width(1.dp))
        Text(
            modifier = Modifier
                .weight(1f)
                .padding(end = 4.dp)
                .wrapContentWidth(Alignment.End),
            text = text2
        )
    }
}

13. Recompose 优化

  1. 无参数的 Composable 函数,在 Recompose 时,不会再次执行函数内的代码
setContent {
        var name by remember { mutableStateOf("allen") }

        Column {
            Button(onClick = {
                name = "amy"
            }) {
                Text(text = "更改文字内容")
            }
            Text(text = name)
            ComposableMethodWithoutParams()
        }
    }
}

@Composable
private fun ComposableMethodWithoutParams() {
    Log.d("TAG", "composableMethodWithoutParams")
    Text(text = "composableMethodWithoutParams")
}

上面的代码中,点击了按钮后,会导致 recompose 操作,但是 ComposableMethodWithoutParams 中的 log 并没有执行,说明,当前函数中代码并没有执行。

  1. 带参数的 Composable 函数,在 Recompose 时,如果所有参数都没有发生改变,也不会再次执行函数内的代码
var flag = 1
        setContent {
            var name by remember { mutableStateOf("allen") }

            Column {
                Button(onClick = {
                    name = "amy"
                }) {
                    Text(text = "更改文字内容")
                }
                Text(text = name)
                ComposableMethodWithoutParams(flag)
            }
        }
    }

    @Composable
    private fun ComposableMethodWithoutParams(result: Int) {
        Log.d("TAG", "composableMethodWithoutParams")
        Text(text = "composableMethodWithoutParams $result")
    }
  1. Structurial Equality(==):Recompose 执行过程中,如果引用的类对象中所有属性都是使用 val 修辞的话,使用的是结构性相等,也就是在判断是否执行某个 Composable 函数中的代码时,判断其参数是否改变使用的是 ==
        var user = User("allen")
        setContent {
            var name by remember { mutableStateOf("allen") }

            Column {
                Button(onClick = {
                    name = "amy"
                    user = User("allen")
                }) {
                    Text(text = "更改文字内容")
                }
                Text(text = name)
                ComposableMethodWithoutParams(user)
            }
        }
    }

    data class User(val name: String)

    @Composable
    private fun ComposableMethodWithoutParams(result: User) {
        Log.d("TAG", "composableMethodWithoutParams")
        Text(text = "composableMethodWithoutParams ${result.name}")
    }

当点击了按钮后,composableMethodWithoutParams 函数中的 log 并没有打印,说明使用的是 ==(equals)来进行判断的。

  1. 对于不可靠的类,也就是类中变量的声明使用的是 var 修辞的类,Recompose 默认使用的是 ===(引用相等)来判断是否需要重新执行的。

为什么对于使用 var 修辞变量的类需要使用 ===(引用相等)呢?

    val user = User("allen")
    val user2 = User("allen")
    var currentUser = user
    setContent {
        Column {
            Button(onClick = {
                currentUser = user2
            }) {
                Text(text = "更改 currentUser 引用")
                currentUser = user2
            }

            showUser(currentUser)
        }

    }
}

private fun showUser(currentUser: User) {
    Log.d("TAG", "currentUser.name = ${currentUser.name}")
}

data class User(var name: String)

看上面的代码,如果这里使用的是 Structural equals 方法的话,当点击按钮将 currentUser 的引用指向 user2 时,由于 user1 和 user2 使用 == 进行比较是相等的,所以,此时 showUser 函数不会被 Recompose。

也就是说,showUser 函数中引用的还是 user1,但是 currentUser 已经指向了 User2,当后面如果对 user2 进行了修改,如果 showUser 里面由于某种原因被 Recompose 了,而不是通过外部导致的 Recompose 的话,由于 showUser 函数中的值引用的还是 user1,而不会更新,这样就与我们想要的结果不符了。

如果想要对使用了 var 修辞的类也使用 ===(结构性相等)的话,可以使用 @Stable 关键字对类进行修辞。当然,上面可能会出现的问题,就需要码农自己来保证了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容