網站首頁 編程語言 正文
前言
因為要寫react定位組件(這不是標題黨,就是完爆ant deisgn的定位組件,你應該看到一半就會同意我的觀點),如下圖:
紅框部分是用絕對定位放在按鈕上面的,你們B端用的主流組件庫都是這樣實現的,它是很多組件的基礎組件,比如下圖:
下拉框組件
select組件
還有什么DataPicker,TreeSelect,Dropdown組件等等的下拉框都是以定位組件為基礎的。
這個組件實現的復雜度在哪
上面提到,這不過就是一個絕對定位嘛(我們假設紅框部分的的dom絕對定位是相較于body元素),我們拿最簡單的情況來看,如下圖,如何把紅框部分渲染到按鈕”更多“的下方呢?
我們可以計算更多按鈕的getBoundingRect(),返回
const reference = {
top: xx, // 按鈕距離瀏覽器頂部的距離
left: xx, // 按鈕距離瀏覽器左邊的距離
width: xx, // 按鈕的寬:沒有padding
height:xx,// 按鈕的高:沒有padding
...等等其他屬性
}
所以紅框部分左上角的坐標就輕易的計算出來了,上面的數據在reference對象上,所以借助reference的定位,我們計算紅框部分的下拉框的定位是在哪
{
position: 'absolute',
top: reference.top + window.pageYOffset // 豎直方向滾動距離 + reference.height
left: reference.left + window.pageXOffset // 橫向滾動距離
}
為啥是上面這么計算呢,假如沒有滾動條滾動,那么紅框部分的絕對定位的top,是不是等于按鈕的距離瀏覽器頂部的高度 + 本身的高度,這個沒問題吧?
然后,如果滾動條滾動了的話,是不是要在上面top的基礎上加上這段距離,就是紅框部分在文檔流絕對定位的top。
好了,到此為止,就是最基本的定位組件的邏輯了,我們接下來看復雜點!
復雜度1
還是拿上面的圖的紅色部分下拉框來說,下拉框一般是在下面,但是我可以定位到上邊吧?左邊,右邊也沒啥吧,再過分點,右上,左下?定位組件要處理對吧
復雜度2
假設,我們定位在下面,我想向左偏移8px,向下偏移3px咋辦,你是不是應該有暴露一個口子
復雜度3
假設我定位在下面,那么我一直滾,馬上就要滾動到看不見下拉框了,如下圖
此時我想讓定位在上面,能不能自動幫我處理?如下圖:
復雜度4
是不是還有可能超出瀏覽器視口了,如下圖:
我們想自動處理,遇到超出就自動變為下方樣子:
復雜度5
此時我定位了一次,但是有可能滾動容器不是window,是另一個div,這個計算咋辦?還有,是不是我滾動的時候,我要監聽滾動事件,還要監聽瀏覽器resize事件,因為我定位的值可能會變?為啥呢,我們上面復雜度3是不是自動幫我們在滾動的時候調整位置
所以你不監聽滾動事件你咋知道要調整位置了?
還有很多細枝末節,比如瀏覽器兼容性等等。。。。
國內組件庫怎么實現這個功能
目前阿里的ant design和字節的arco design都是自己實現的,我們拿arco來看(ant內部叫rc-trriger組件,arco叫trriger組件),面向過程的代碼,看的我頭皮發麻。。。我截個圖:
上面的代碼屬于把我們提到的復雜度全部揉在了一起。
flouting-ui為啥代碼質量比ant高
它是以中間件的形式去處理的,思路是什么呢?它假設最開始有一個 computePosition函數,我們假設上面提到的復雜度都沒有,也就是不考慮的前提下,我們怎么計算定位組件的坐標,也就是我們最前面的圖里說的,紅色框部分絕對定位的的的top值和left值:
API如下:
computePosition(要掛載的dom節點,下拉框組件,參數...)
然后我們剛才提到的復雜度,它分別用中間件的形式去處理,比如復雜度2,是想定位之后還有點偏移量,flouting-ui咋做的呢
import {computePosition, offset} from '@floating-ui/dom';
// referenceEl: 要掛載的dom節點
// floatingEl:下拉框組件(或者說想要掛載到上面referenceEl的dom元素)
computePosition(referenceEl, floatingEl, {
middleware: [offset(10)],
});
如上,offset就是一個中間件,offset(10),就是向左偏移10px
好了,如果想處理復雜度3呢,我們用另一個中間件
import {computePosition, flip} from '@floating-ui/dom';
computePosition(referenceEl, floatingEl, {
middleware: [flip()],
});
這樣就自動處理了,是不是很簡單啊
其實所有這些復雜度的解決方案,在flouting-ui里都是以中間件的形式去處理的,還可以傳多個中間件解決多個問題。
中間件的形式好在哪
那么我們就可以自定義很多中間件了,也就是你的組件不僅僅提供了很多功能,解決了很多常用的問題,你還允許用戶寫代碼去拓展,試問,現在哪個組件庫的代碼是這么寫的?沒有吧?
代碼中間件原理
我們先看看flouting-ui的computePosition API是怎么實現的,它是flouting-ui的核心方法,是串聯所有中間件的基礎。
下一篇寫完整的源碼(很晦澀,估計也沒幾個人看,所以這期就不寫了),理解起來說實話,你不熟悉原生dom的話有點困難,比如說為啥這個庫要用window.pageYoffset而不是document.body.scrollTop去獲取瀏覽器html元素的滾動距離,因為document.body.scrollTop固定為0,取不到。。。
核心思路講解:我們還是拿下圖做類比
let {x, y} = 求出紅色框里的下拉框絕對定位的x坐標和y坐標
// 記錄原始placement
let statefulPlacement = placement;
// 所有中間件導出的值都掛載到下面的對象上
let middlewareData: MiddlewareData = {};
// 數據經過middleware的處理
// middleware是一個數組,存放所有中間件,就是我們上面說的處理每一個復雜度的對象
for (let i = 0; i < middleware.length; i++) {
// name是中間件的名字,fn是處理復雜度的邏輯
const {name, fn} = middleware[i];
// 通過把最前面計算的x,y經過fn的處理,得到了新的x,y的值
// data是指返回的數據,想讓后面的中間件也能訪問到的數據
/**
* 每個middleware需要返回
* x 新的x坐標
* y 新的y坐標
* data
* reset
*/
const {
x: nextX,
y: nextY,
data,
reset,
} = await fn({
/**
* 每個middleware收到的參數
* x 目前的x坐標
* y 目前的y坐標
* initialPlacement 最初傳入的placement
* placement
* middlewareData middleware返回的額外數據
*/
x,
y,
initialPlacement: placement,
placement: statefulPlacement,
strategy,
middlewareData,
rects,
platform,
elements: {reference, floating},
});
x = nextX ?? x;
y = nextY ?? y;
// 每次處理后的數據想要讓后面的中間件訪問,就需要掛載到middlewareData對象
// 這個對象非常好啊,用name隔離了作用域
middlewareData = {
...middlewareData,
[name]: {
...middlewareData[name],
...data,
},
};
rest的處理邏輯。。。省略,不是很重要
最后return出被處理完的x,y坐標,或者自動幫我們監聽滾動事件和resize事件,然后拿著x,y就可以賦在css的絕對定位的top和left上,實現了定位。
每次處理后的數據想要讓后面的中間件訪問,就需要掛載到middlewareData對象,這個對象非常好啊,用name隔離了作用域,這就是比koa這個框架處理的高明之處,koa里的ctx對象就像一個垃圾桶,什么屬性都往上面掛載,掛載太多了,你也不知道是哪個中間件掛載的
所以flouting-ui的處理思路給我打開了新的思路!nice!!!
中間件如何寫
源碼再開一篇文章寫,這里看看就好,不用過多去關系代碼
export const offset = (value: Options = 0): Middleware => ({
name: 'offset', // 中間件名字
options: value, // 傳給中間件的值
async fn(middlewareArguments) { // 中間件處理函數
const {x, y} = middlewareArguments;
const diffCoords = await convertValueToCoords(middlewareArguments, value);
return {
x: x + diffCoords.x,
y: y + diffCoords.y,
data: diffCoords,
};
},
});
本文結束,所以如果市面上的組件庫的每個組件都是這個形式暴露給用戶,就是提供插件式的自定義的中間件,那么整個組件庫的拓展性可以說碾壓市面上國內所有的react的組件庫了
原文鏈接:https://juejin.cn/post/7171054283591254029
相關推薦
- 2022-03-21 Prometheus容器化部署的實踐方案_docker
- 2022-04-10 C++?反匯編之關于Switch語句的優化措施_C 語言
- 2022-09-20 linux系統下用.sh文件執行python命令的方法_linux shell
- 2022-10-18 Go項目怎么使用枚舉_Golang
- 2021-12-14 linux下多線程中的fork介紹_Linux
- 2022-10-08 react-redux的基本使用_React
- 2023-02-10 Jupyter導入自定義模塊及導入后TypeError錯誤問題及解決_python
- 2023-05-08 Android開發多手指觸控事件處理_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支