在Compose中父组件如何调用子组件的函数?

原文地址:# 在Compose中父组件如何调用子组件的函数?

咋一看标题你可能会觉得这有什么好研究的,请仔细看我的描述:在父组件 中调用子组件的函数!

众所周知,如果我们希望让子组件 调用父组件的函数,可以如下方式:

那么问题来了,父组件如何调用子组件的函数呢?

Compose 不同于传统的 View 体系,每一个组件都是@Composable注解的函数,没有实例对象,无法对外暴露函数。

你可能会说,父组件调用子组件函数有必要么?

试想这样一个场景,我们有一个复杂的展示页面,里面有 banner轮播图、用户信息、资讯List 等等,他们被放在一个大的可滑动组件中,现在我们要实现这个可滑动组件下拉刷新。

这时事件不再是有子组件发出(不同于"状态向下、事件向上"),而是父组件发出的,此时应该如何处理?

一个简单的例子:

kotlin 复制代码
@Composable
fun Container() {
    Column {
        TButton("refresh"){
            TODO() //如何实现
        }
        (0..10).map {
            SubComponent(index = it)
        }
    }
}

@Composable
fun SubComponent(index:Int) {
    val (state, setState) = useState(0.0)
    //刷新函数在子组件
    fun refresh() {
        setState(Random.nextDouble())
    }
    Column {
        Row {
            Text(text = "index $index: $state")
            TButton(text = "refresh") {
                // 子组件可以轻松的刷新自己
                refresh()
            }
        }
        Divider(modifier = Modifier.fillMaxWidth())
    }
}

方法1:继续状态提升↑!!

我们可以继续贯彻状态提升的思想,父组件的刷新只改变顶层状态,由子组件监听状态变化,实现刷新:

kotlin 复制代码
@Composable
fun Container() {
    // 状态提升到父组件
    val (isRefresh, _, setIsRefresh) = useBoolean(false)
    val scope = rememberCoroutineScope()
    Column {
        TButton("refresh") {
            scope.launch {
                setIsRefresh(true)
                delay(1.seconds)
                setIsRefresh(false)
            }
        }
		//.............
    }
}

@Composable
fun SubComponent(index: Int, refreshState: Boolean) {
    val (state, setState) = useState(0.0)
    fun refresh() {
        setState(Random.nextDouble())
    }
    // 子组件监听状态
    useEffect(refreshState) {
        if (refreshState) {
            refresh()
        }
    }
    //.............
}

这样做当然是OK的,代码也可以完美运行,但是我们需要给所有组件加上新的参数,以及 useEffect 监听状态变化;

原始代码同样存在我在上一篇文章中提到的问题,一旦层级过多,就会存在大量中间传递,如果修改就牵一发动全身。

如果你想用这种方法,我建议使用 useContext 进行优化:

kotlin 复制代码
val RefreshContext = createContext(false)

@Composable
fun Container() {
    //.............
    Column {
        TButton("refresh") {
           //.............
        }
        // 通过 Provider 向子组件暴露状态
        RefreshContext.Provider(isRefresh){
            (0..10).map {
                SubComponent(index = it)
            }
        }
    }
}

@Composable
fun SubComponent(index: Int) {
    //.............
    // 刷新状态通过 useContext获取
    val refreshState =useContext(context = RefreshContext)
    useEffect(refreshState) {
        if (refreshState) {
            refresh()
        }
    }
    //.............
}

方法2:使用 useEvent

为什么叫这个名字是因为它的使用有一点类似 EventBus 的订阅发布模式,我们先来看改造后的代码

kotlin 复制代码
@Composable
fun Container() {
    val post = useEventPublish<Unit>()
    Column {
        TButton("refresh") {
            post(Unit)
        }
        (0..10).map {
            SubComponent(index = it)
        }
    }
}

@Composable
fun SubComponent(index: Int) {
    val (state, setState) = useState(0.0)
    fun refresh() {
        setState(Random.nextDouble())
    }
    useEventSubscribe { _: Unit ->
        refresh()
    }
    //.............
}

使用方法大致如下:

  1. 使用 useEventPublish<Unit>() 拿到一个 post 发布函数,

    尖括号中的是我们需要传递的Event参数类型,由于这里只是简单演示,我直接使用了 Unit

  2. 在需要订阅的事件的组件中使用useEventSubscribe注册一个函数

    注意在闭包中声明正确的类型!

  3. 发布者调用 post 函数,发布事件

与使用 useContext 相似,我们可以用它来解耦父子组件之间的关系,无需担心层级嵌套的问题。

但是同时也要注意,useEventSubscribe 在组件不可见时是自动反注册的,所以需要在可见范围内才能正确的响应。

现在我们可以回答标题的问题了,父组件如何调用子组件函数:

  1. 无法直接调用,因为组件是函数
  2. 通过状态提升,父组件修改状态。子组件监听状态变化间接调用
  3. 使用useEvent相关钩子,通过订阅发布模式,解耦组件关系、实现跨组件通信

Tips

Tips: useEvent 更多是为了用于跨组件通信,并不只是用来解决父组件调用子组件函数哦,post 函数可以传递任意实例。它和 EventBus 是几乎相同的。相同事件类型的订阅者,会接收到相同的事件发布。

探索更多

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks

kotlin 复制代码
implementation("xyz.junerver.compose:hooks:1.0.5")

欢迎使用、勘误、pr、star。

相关推荐
Lstone736423 分钟前
Bitmap深入分析(一)
android
一起搞IT吧1 小时前
Android功耗系列专题理论之十四:Sensor功耗问题分析方法
android·c++·智能手机·性能优化
ByNotD0g2 小时前
Doris 学习笔记
android·笔记·学习
修炼者2 小时前
【Android进阶】 RenderEffect的底层实现
android
bropro2 小时前
MySQL不使用子查询的原因
android·数据库·mysql
执笔论英雄3 小时前
【cuda】 pinpaged
android·java·数据库
新青年.3 小时前
Android(Compose)使用 LibVLC 播放 RTSP 视频流
android
一见4 小时前
WorkBuddy安装Skill的方法
android·java·javascript
毛骗导演4 小时前
万字解析 OpenClaw 源码架构-跨平台应用之Android 应用
android·前端·架构