在React Native 中以指定父元素绝对定位子元素,打破View 的Position 限制
在React Native 中以指定父元素绝对定位子元素,打破View 的Position 限制
Hi!大家好,我是神Q超人!最近因为工作的关系,又重回React Native 的怀抱,虽然React Native 主打可以用贴近开发Web 的语法去写手机APP,但其实真正开发起来面临到的坑还是挺多的。这篇文章要分享的绝对定位就是在React Native 内排版时,遇到的状况,感觉网路上资源不多,就写下文章来记录一下处理的过程啰!
这篇文章讲述的定位是CSS 内的position 属性,如果对这个属性还不熟的读者,可以先看一下之前写过的文章: CSS | 所以我说那个版能不能好切一点? — Position 基本用法 ,如果准备好的话,就开始吧!
位置
在开始讲解法之前,先来说说在Web 上预期的排版到React Native 会变成什么样子吧!假如我有这么一段HTML 和CSS:
在这段HTML 里分别有root、parent 和children 三个div,从排版来看,虽然children 在parent 里面,但因为children 的 位置:绝对
会使它的定位效果会对齐外层第一个遇到的position 为 绝对
或 相对的
的div,也就是root。实际画面会长这样子:
https://codepen.io/ms314006/pen/rNdYXyM
从上方的结果可以更明显的看出,children 的位置与它是否在parent 内无关,当然,如果你把 位置:相对
从root 移到parent 的话那又是另一回事了。
那在React Native 的状况呢?如果我有这么一段React Native 的程式码:
CSS 的部分基本上都没有改变,但是在React Native 的定位方式却有所不同,children 的定位标准似乎是以parent 为主的:
为什么会这样子呢?让我们看看React Native 上的文件怎么说:
https://reactnative.dev/docs/layout-props#position
根据文件上说,虽然React Native 的position 就像正常的CSS,但position 的预设值都是 相对的
,这意味在React Native 里所有子元素的定位都是相对于父元素。
那该怎么办?在寻寻觅觅下只有在StackOverflow 上找到 这篇问答 , 虽然只有一篇,至少它给了我们很棒的方向来解决这个问题。其中一个解法是改变排版的结构,让children 直接放在要定位的元素下方:
另外一个就很有趣了,在答案上,是直接说可以使用 反应本机纸 里面的 门户网站 ,但我个人其实不太想要为了小小的排版需求而导入整个component library,因此就决定自己来做一个简单的Portal,让子元素可以定位在指定的父元素,这也是这篇文章主要想讲的内容!
门户网站
既然要实作 反应本机纸 提供的功能,阅读原始码还是必要的过程,只要能够读出思路,那就有办法实作出一个最小的可行方案!但毕竟嘛,这篇文章不是开源专案读起来的系列,因此下方只会讲解我如何实现的,不会带大家看 反应本机纸 的原始码,如果大家有兴趣的话可以到 这里阅读Portal 的原始码 。
以下我会分成四个部分,讲解我是如何让子元素穿过父元素的, 主要的思路是希望能把子元素render 的地方拉出来,render 到要定位的父元素下层 。听起来有点抽象,接下来就直接进入实作吧!
门户主机
首先建立一个 门户主机
, 门户主机
的目的是要render 父元素,让我们之后能够直接添加render 子元素逻辑到 门户主机
中,如果成功的话就能够将那些子元素跳脱,render 到与父元素同一层的位置:
接下来要替Portal 的子元素们写下逻辑,在实作里面,我把那些子元素称呼为consumer,每一个consumer 都是代表要被跳脱的子元素,因此我需要有一些在Portal 相关component 中管理consumer 的方法:
上方的多包装一层 获取门户方法
取得方法们的原因是希望每一个 门户主机
里的consumer 都互不相干扰,所以才每次都重新取新的。
PortalContext
取出 门户方法
后,用 React 提供的[ 使用上下文](https://zh-hant.reactjs.org/docs/hooks-reference.html#usecontext)
把这些 门户方法
往下传,用useContext 的好处有两个,一是不需要透过props 一层层传递 门户方法
,二是我们也不晓得到底会有哪些子元素需要使用到 门户方法
,所以就直接放进context 里,让需要的子元素直接取用。
建立 PortalContext
后,就能回到 门户主机
中 import 使用:
除了 门户方法
外,我还用 使用状态
建立另一个 使成为
的方法,因为 门户方法
是个物件,在 门户方法
产生后,该物件也永远不会改变(会变的只有里面的 消费者
而已),因此要有个方法能够让我们控制画面的render,至于 使成为
在什么时候用到,我们待会会再提到。
门户消费者
处理完父元素render 逻辑的 门户主机
后,就接着看看处理子元素的 门户消费者
吧:
在 门户消费者
里,主要的目的是将children 存到 门户方法
里面的 消费者
中,并用 使用效果
让children 可以在开始和结束时用 设置消费者
和 删除消费者
操作 消费者
, 使成为
也是在这里执行的的。另一方面,每个 消费者
对应到的 消费者密钥
是保管在 使用参考
中,利用此特性,就算children 变化导致 使成为
执行,也不会再建立一个新的key,以相同的key 让children 的新内容可以在 消费者
中被更新,而不是塞新的内容到 消费者
中。
最后一件要注意的是,因为 门户消费者
对 门户主机
其实是有依赖关系的,为了怕后人不正确的使用,就在 使用效果
内的第一句就先判断是否有 门户方法
存在,没有的话就直接报错,并显示对应的错误讯息。
门户管理器
将把子元素放进 消费者
,以及更新的逻辑在 门户消费者
写好后,就可以接着处理如何render 消费者
内的子元素,这里我们新建立一个 门户管理器
:
门户管理器
相对单纯,就是从 PortalContext
中取得从 门户主机
传递的资料,并把 消费者
内的子元素都render。完成后回到 门户主机
中,放入 门户管理器
就万事搞定啦!
最后来看看 门户主机
和 门户消费者
使用起来的效果吧!首先将最一开始的程式码修改为:
在看结果前,先来整理一下我们的逻辑:
门户主机
会把parent 全部原封不动的render。- 被
门户消费者
包住的children 并不会被render,而是收到门户主机
内部的PortalContext
的消费者
内,并回传一个null。 - 被放进
消费者
的内容会透过门户管理器
,把它们render 到和门户主机
同一层的位置。
这么一来,children 被render 的位置就会和parent 在同一层,position 也就不会对齐在parent 身上,而是root:
当然上方实作的Portal 一定还有许多能够改进的地方,例如该如何更让 门户主机
和 门户消费者
的依赖关系更明显,以及如果有巢状的定位需求该怎么办等等。
这些问题在经过和强者同事们的讨论后,有得出一些改良方式,一个是在 门户主机
和 门户消费者
使用时加入name 来绑定依赖关系,这样就能够知道 门户消费者
目前是会render 到哪个 门户主机
,在Debug 时也比较好找,另一个方法是像 使用状态
等hooks,透过一个hooks 产生 门户主机
和 门户消费者
来表达依赖关系。
以上两个都是很棒的方式,但因为还没有遇到实际的问题,至今我也还没开始改良就是了,如果大家有其他想法也可以留言告诉我哦!
这应该是我近期做过自己觉得最有趣的一件事情了,从一开始发现这件事情,到思考如何处理,这一切都非常有趣,我依稀记得写完上面那些程式码的时候大概是快一点,而且隔天还要6 点起床搭高铁,不过从写完后到Demo 给同事看时就一直非常开心,有种「哇!这样子不断子思考的感觉真棒!能成为工程师真好」
最后如果文章中有任何问题,或是解释不清楚的地方,再麻烦大家留言告诉我,所有留言都非常感激!
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通