聊聊 Jetpack Compose 原理 -- 穿透刺客 CompositionLocal

Compose 官方说明一直很简洁:CompositionLocal 是通过组合隐式向下传递数据的工具。


我们先来看一段代码:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {val name = "Hi, Compose!"    // name 是局部变量ShowMessage(name)}}}
}@Composable
fun ShowMessage(message: String) {Text(message)
}

这段代码中局部变量 name 被暴露出来了,这就是我们常见的 State Hoisting(状态提升)。

🤔 🤔 现在思考两个问题:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {val name = "Hi, Compose!"ShowMessage(name)}👉🏻 name =     // 思考 1: 这边能不能获取到 name?}}
}@Composable
fun ShowMessage(message: String) {👉🏻 name =             // 思考 2: 这边能不能获取到 name?Text(message)
}

答案是:肯定不行!因为 name 这个局部变量的 作用域 是限定在 ComposeBlogTheme {} 范围内的。

那么你可能会有疑惑,为什么要思考这么常识的问题?我现在把代码改一下:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {val name = "Hi, Compose!"// ShowMessage(name)    // 这段代码我不要了}}}
}@Composable
fun ShowMessage() {    // 参数我也去掉,不需要外面传参进来Text(name)         // 我就直接把外面的 name 写到这里,直接用
}

这样写行吗?肯定是不行的!但我既然这么写了,肯定是有目的的。如果我们的代码这样写,还希望它可以运行,并且结果正确!那么也就意味着 name 这个局部变量拥有了可以穿出它的 作用域,并且穿透进 showMessage 函数的能力!

其实我们可以实现这样的效果,主角登场:CompositionLocal,它就提供了这种 「穿透能力」


创建 CompositionLocal 实例

首先创建一个具有 「穿透能力」 的变量:

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {val name = "Hi, Compose!"}}}
}@Composable
fun ShowMessage() {Text(name)
}// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> { "Compose" }

Compose 中,一般定义这种具有「穿透能力」CompostionLocal,它的名称有个约定俗成的写法:LocalXXX


为 CompositionLocal 提供值

CompostionLocal 实例定义好了,我们要通过 provides 给它绑定一个值。

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {val name = "Hi, Compose!"// 2. CompositionLocalProvider 可组合项可将值绑定到给定层次结构的 CompositionLocal 实例CompositionLocalProvider(LocalName provides name) {ShowMessage()}}}}
}@Composable
fun ShowMessage() {Text(name)
}// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> { "Compose" }

获取 CompositionLocal 数据

前面两步其实就已经创建好了一个包含 数据 + 可穿透CompositionLocal 了,最后一步就是获取到其中的数据。

class MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {val name = "Hi, Compose!"// 2. CompositionLocalProvider 可组合项可将值绑定到给定层次结构的 CompositionLocal 实例CompositionLocalProvider(LocalName provides name) {ShowMessage()}}}}
}@Composable
fun ShowMessage() {// 3. 获取 nameText(LocalName.current)
}// 1. 通过 compostionLocalOf 创建 CompositionLocal
val LocalName = compositionLocalOf<String> { "Compose" }

👌🏼👌🏼👌🏼,搞定!


CompositionLocal 应用场景

提供上下文

LocalContext.current    // 是不是很熟悉?这其实就相当于 getContext()

MaterialTheme

其实官方的 MaterialTheme 就用到了 CompositionLocal,我们看源码:

@Composable
fun MaterialTheme(colors: Colors = MaterialTheme.colors,typography: Typography = MaterialTheme.typography,shapes: Shapes = MaterialTheme.shapes,content: @Composable () -> Unit
) {val rememberedColors = remember { colors.copy() }.apply { updateColorsFrom(colors) }val rippleIndication = rememberRipple()val selectionColors = rememberTextSelectionColors(rememberedColors)// 通过 providers 将 rememberedColors 提供给了 LocalColorsCompositionLocalProvider(LocalColors provides rememberedColors,LocalContentAlpha provides ContentAlpha.high,LocalIndication provides rippleIndication,LocalRippleTheme provides MaterialRippleTheme,LocalShapes provides shapes,LocalTextSelectionColors provides selectionColors,LocalTypography provides typography) {ProvideTextStyle(value = typography.body1) {PlatformMaterialTheme(content)}}
}
object MaterialTheme {val colors: Colors@Composable@ReadOnlyComposableget() = LocalColors.currentval typography: Typography@Composable@ReadOnlyComposableget() = LocalTypography.currentval shapes: Shapes@Composable@ReadOnlyComposableget() = LocalShapes.current
}

compositionLocalOf / staticCompositionLocalOf 的区别

前面的例子我们使用 compositionLocalOf 创建 CompositionLocal,另外官方还提供了 staticCompositionLocalOf


📌 compositionLocalOf:在重组期间更改提供的值只会使读取其 current 值的内容无效。


📌 staticCompositionLocalOf:与 compositionLocalOf 不同,Compose 不会跟踪 staticCompositionLocalOf 的读取。更改该值会导致提供 CompositionLocal 的整个 content lambda 被重组,而不仅仅是在组合中读取 current 值的位置。


😵😵😵,很懵???我们来看下面的代码:

compositionLocalOf

class MainActivity : ComponentActivity() {private val themeBackground by mutableStateOf(Color.Blue)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {CompositionLocalProvider(LocalBackground provides Color.Red) {TextBackground()CompositionLocalProvider(LocalBackground provides themeBackground) {/*** 2. themeBackground 变化后,仅此范围重组* TextBackground()*/CompositionLocalProvider(LocalBackground provides Color.Red) {TextBackground()}}TextBackground()}}}}
}@Composable
fun TextBackground() {Surface(color = LocalBackground.current) {Text("Hi, Compose")}
}// 1. Compose 会记录跟踪每一个调用 LocalBackground.current 的区域
val LocalBackground = compositionLocalOf { Color.Blue }

staticCompositionLocalOf

class MainActivity : ComponentActivity() {private val themeBackground by mutableStateOf(Color.Blue)override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {ComposeBlogTheme {CompositionLocalProvider(LocalBackground provides Color.Red) {TextBackground()CompositionLocalProvider(LocalBackground provides themeBackground) {/*** 2. themeBackground 变化后,内部所有 content 全部重组* TextBackground()** CompositionLocalProvider(LocalBackground provides Color.Red) {*    TextBackground()* }*/}TextBackground()}}}}
}@Composable
fun TextBackground() {Surface(color = LocalBackground.current) {Text("Hi, Compose")}
}// Compose 不会记录跟踪每一个调用 LocalBackground.current 的区域
val LocalBackground = staticCompositionLocalOf { Color.Blue }

区别搞懂了吧?那么随之而来一个疑问:我们定义 CompositionLocal 该用哪个?

官方说明

如果为 CompositionLocal 提供的值发生更改的可能性微乎其微或永远不会更改,使用 staticCompositionLocalOf 可提高性能。


比如,我们看两个系统里面定义好的 CompositionLocal

// 上下文固定不变,用 staticCompositionLocalOf
val LocalContext = staticCompositionLocalOf<Context> {noLocalProvidedFor("LocalContext")
}// 内部内容颜色常变,用 compositionLocalOf
val LocalContentColor = compositionLocalOf { Color.Black }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/214421.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

datav-轮播排名-对数据进行处理

前言 对于大屏需求我们排名数据轮播也是经常需要用到的需求&#xff0c;datav也是给我们提供了 不是说我们自己不能写&#xff0c;而是提供好的轮子比我们自己 写的&#xff0c;更全面&#xff0c;更周到&#xff0c; 没有特殊需求的话&#xff0c;使用datav配置一下完成这个…

mysqlsh导入json,最终还得靠navicat导入json

工作需要将一个巨大的10G的json导入mysql数据库。 看到mysql官方有对json导入的支持。 如下&#xff1a; MySQL :: Import JSON to MySQL made easy with the MySQL Shell $ mysqlsh rootlocalhost:33300/test --import /path_to_file/zips.json Creating a session to root…

产品经理进阶:以客户为中心的8个维度

目录 简介 以客户为中心 流程和组织维度 产品维度 CSDN学院《硬件产品进阶课》

python:六种算法(DBO、RFO、WOA、GWO、PSO、GA)求解23个测试函数(python代码)

一、六种算法简介 1、蜣螂优化算法DBO 2、红狐优化算法RFO 3、鲸鱼优化算法WOA 4、灰狼优化算法GWO 5、粒子群优化算法PSO 6、遗传算法GA 二、6种算法求解23个函数 &#xff08;1&#xff09;23个函数简介 参考文献&#xff1a; [1] Yao X, Liu Y, Lin G M. Evolution…

读书笔记 | 自我管理的关键是提高执行力

哈喽啊&#xff0c;你好&#xff0c;我是雷工&#xff01; 有句话说&#xff0c;能管好自己才是真的本事。 自我管理&#xff0c;管好自己很重要。 我们之所以懂得这么多的道理&#xff0c;却依然过不好这一生&#xff1f; 很大部分原因是因为管不住自己&#xff0c;做不到。 …

性能测试基础

性能测试分类 客户端性能&#xff1a;测试APP自身的性能&#xff0c;例如CPU、内存消耗&#xff1b;web页面元素渲染速度 服务端性能&#xff1a;测试服务端项目程序的支持的并发、处理能力、响应时间等&#xff0c;主要通过接口来做性能测试 性能测试指标 并发 同时向服务…

大一作业习题

第一题&#xff1a;答案&#xff1a; #include <stdio.h> void sort(int a[], int m) //将数组a的前m个元素(从小到大)排序 {int i 0;for (i 0; i < m - 1; i){int j 0;int flag 1;for (j 0; j < m - 1 - i; j){if (a[j] > a[j 1]){int t 0;t a[j];…

Java八股文面试全套真题【含答案】- Servlet篇

以下是一些关于Servlet的经典面试题以及它们的答案&#xff1a; 什么是 Servlet&#xff1f; 答案&#xff1a;Servlet 是运行在服务器上&#xff0c;用于处理客户端请求并生成响应的 Java 类。 Servlet 和 JSP 之间的区别是什么&#xff1f; 答案&#xff1a;Servlet 是基于…

1.鸿蒙应用程序开发app_hap开发环境搭建

1.下载Node.js, Javascipts的运行环境 node.js版本下载v12.18.3/https://www.cnblogs.com/txwtech/p/17865780.html 2.下载并安装DevEco Studio DevEco Studio 3.1 DevEco Studio 3.1配套支持HarmonyOS 3.1版本及以上的应用及服务开发&#xff0c;提供了代码智能编辑、低代…

Docker笔记:Docker中简单配置Mysql/Redis/Mongodb容器

Docker 配置 Mysql 容器 1 &#xff09;方案1&#xff1a;基于centos等linux操作系统 启动centos镜像&#xff0c;在里面安装 mysql这样比较麻烦&#xff0c;配置的东西很多 … 2 &#xff09;方案2&#xff1a;直接用 mysql 镜像 (推荐) $ docker pull mysql 下载镜像$ do…

589. N 叉树的前序遍历

589. N 叉树的前序遍历 java1&#xff1a;stack栈&#xff1a;没看懂 class Solution {public List<Integer> preorder(Node root) {List<Integer> res new ArrayList<Integer>();if (root null) {return res;}Map<Node, Integer> map new HashMa…

C盘瘦身,C盘清理

以下只是我的C盘清理经验~ 一.【用软件简单清理C盘】 使用一些垃圾清理软件&#xff0c;简单的初步把C盘先清理一遍。&#xff08;这种软件太多我就不推荐了……&#xff09; 二.【WPS清理大师】 因为我电脑装了WPS&#xff0c;发现右键单击C盘有个选项【释放C盘空间】&#xf…

接口自动化框架(Pytest+request+Allure)

前言&#xff1a; 接口自动化是指模拟程序接口层面的自动化&#xff0c;由于接口不易变更&#xff0c;维护成本更小&#xff0c;所以深受各大公司的喜爱。 接口自动化包含2个部分&#xff0c;功能性的接口自动化测试和并发接口自动化测试。 本次文章着重介绍第一种&#xff0c…

Vue3.3.4中watch无法监测props的更改

背景 网上说了很多解决方案&#xff0c;都是通过watch(() > props.value, (newValue, oldValue) > {})解决&#xff0c;或者是加上{deep: true}附加属性。但是我在Vue3.3.4中&#xff0c;还是无法解决。 下面说一下我的解决方案。 解决方案 通过父组件调用子组件defineE…

点云/Mesh 常见处理库和软件汇总

注&#xff1a;参考 网址1、网址2 文章目录 软件通用点云/Mesh处理库通用几何处理库专用功能库 软件 Processing MeshLabCloudCompareTrimeshPyVistaVedo Visualization Simple-3dvizPlotOptiX (Requires CUDA-enabled GPU)PolyscopePyrender 通用点云/Mesh处理库 PCL &am…

【开发问题】vue的前端和java的后台,用sm4,实现前台加密,后台解密

sm4加密 vue引入的包代码加密解密 javamaven代码运行结果 vue 引入的包 npm install sm-crypto代码加密解密 加密&#xff1a; key &#xff1a;代表着密钥&#xff0c;必须是16 字节的十六进制密钥 password &#xff1a;加密前的密码 sm4Password &#xff1a;代表sm4加密…

Python之格式化保存数据点

功能&#xff1a;将平面点集存储为格式化txt文档&#xff0c;每个坐标值为5位整数&#xff0c;前三位为整数&#xff0c;后2位为小数 输入&#xff1a;平面点坐标&#xff0c;用列表存储&#xff0c;列表的元素为点坐标元组 输出&#xff1a;txt文件&#xff0c;每行一个点坐…

【计算机网络】应用层电子邮件协议

一、电子邮件系统架构 电子邮件是一个典型的异步通信系统&#xff0c;发送方从UA&#xff0c;也就是邮件客户端&#xff0c;通过应用层SMTP协议&#xff0c;传输层tcp协议&#xff0c;发送给发送方的邮件服务器&#xff0c;比如使用的是163邮箱&#xff0c;163提供的SMTP服务器…

python中实现yaml文件管理参数

yaml参数管理器 这是文件目录关系&#xff0c;其中config存放.yaml文件&#xff0c;scripts存放py文件 然后就可以自由使用了&#xff1a; import yaml import os#获取路径 script_directory os.path.dirname(os.path.abspath(__file__)) # 相对于脚本文件的路径 image_relat…

Swift “黑魔法”之动态获取类实例隐藏属性的值

概览 在 Swift 代码的调试中,我们时常惊叹调试器的无所不能:对于大部分“黑盒”类实例的内容,调试器也都能探查的一清二楚。 想要自己在运行时也能轻松找到 Thread 实例“私有”属性的值吗(比如 seqNum)? 在本篇博文中您将学到如下内容: 概览1. 借我,借我,一双慧眼吧…