在React Native 中以指定父元素绝对定位子元素,打破View 的Position 限制

在React Native 中以指定父元素绝对定位子元素,打破View 的Position 限制

Photo by 绝对视觉 on 不飞溅

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。完成后回到 门户主机 中,放入 门户管理器 就万事搞定啦!

最后来看看 门户主机 门户消费者 使用起来的效果吧!首先将最一开始的程式码修改为:

在看结果前,先来整理一下我们的逻辑:

  1. 门户主机 会把parent 全部原封不动的render。
  2. 门户消费者 包住的children 并不会被render,而是收到 门户主机 内部的 PortalContext 消费者 内,并回传一个null。
  3. 被放进 消费者 的内容会透过 门户管理器 ,把它们render 到和 门户主机 同一层的位置。

这么一来,children 被render 的位置就会和parent 在同一层,position 也就不会对齐在parent 身上,而是root:

当然上方实作的Portal 一定还有许多能够改进的地方,例如该如何更让 门户主机 门户消费者 的依赖关系更明显,以及如果有巢状的定位需求该怎么办等等。

这些问题在经过和强者同事们的讨论后,有得出一些改良方式,一个是在 门户主机 门户消费者 使用时加入name 来绑定依赖关系,这样就能够知道 门户消费者 目前是会render 到哪个 门户主机 ,在Debug 时也比较好找,另一个方法是像 使用状态 等hooks,透过一个hooks 产生 门户主机 门户消费者 来表达依赖关系。

以上两个都是很棒的方式,但因为还没有遇到实际的问题,至今我也还没开始改良就是了,如果大家有其他想法也可以留言告诉我哦!

这应该是我近期做过自己觉得最有趣的一件事情了,从一开始发现这件事情,到思考如何处理,这一切都非常有趣,我依稀记得写完上面那些程式码的时候大概是快一点,而且隔天还要6 点起床搭高铁,不过从写完后到Demo 给同事看时就一直非常开心,有种「哇!这样子不断子思考的感觉真棒!能成为工程师真好」

最后如果文章中有任何问题,或是解释不清楚的地方,再麻烦大家留言告诉我,所有留言都非常感激!

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/20642/59470623

posted @   哈哈哈来了啊啊啊  阅读(724)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示