(給前端大全加星標,提升前端技能)
作者:前端勸退師 公號 / 前端勸退師
前言在某個月黑風高的晚上...沒劇刷的我無意想起以前處理的一些彈窗的坑。
然後又無意間刷到「Portal」,才知道Modal的實現還有如此妙的方式,順而想著乾脆把UI組件庫的實現原理看完。
本文將講述 Modal彈窗類的實現原理:
1.Modal彈窗的基本原理我給彈窗類的定義是脫離固定的層級關係,不再受制於層疊上下文的組件。
常見的Modal模態框、Dialog對話框、Notification通知框等都是最最常用的交互方式。
在我們頁面有時需要一些特定的彈窗時,通過改UI組件過於麻煩。
這時切圖仔級別的會想:簡單啊,創建一個<div/> 給絕對定位不就得了。
倘若只是當前路由頁用,也還湊合。「可一旦涉及到了組件復用以及抽象為聲明式,就會有很大的隱患」:
即使封裝了,都是在每個路由頁下創建<div/>,易造成樣式汙染。再進一步想,萬一組件庫會作為績效考核,拿到每個環境都長得不一樣,咋整?1.1 Jquery時代的彈窗實現初初入行時,去各種資源站,找Jquery的UI組件,想必三四年經驗的前端們都曾樂此不疲。
這個時代(也就三四年前)的彈窗,因為沒有React/Vue根節點的概念,普遍都是:
「直接操作真實 dom,使用熟知的 dom 操作方法將指令所在的元素 append 到另外一個 dom 節點上去。」 如:document.body.appendChild。再通過overflow: hidden或display:none(或調整z-index)來隱藏。這種操作真實dom的代價,在大型項目中不停觸發重繪/回流,是很糟糕的,且內部數據/樣式不易更改。像以下這種情況就容易出現:
原本圖片固定在區域內。小彈窗展示後,溢出了。隨著React / Vue先進庫的發展,也陸續有了多種方案選擇。。。1.2 React / Vue早期實現。其實React / Vue早期的實現和Jquery時代的並無二異:「依賴於父節點數據,在當前組件內掛載彈窗。」
Vue的情況稍好,有自定義指令這條路走。
❝以下引自:《Vue 中的 Portal 技術》
❞以vue-dom-portal為例,代碼非常簡單無非就是將當前的 dom 移動到指定地方:
可以看到在 inserted 的時候就拿到實例的 el(真實 dom),然後進行替換操作,在 componentUpdated 的時候再次根據指令的值去操作 dom。
為了能夠在不同聲明周期函數中使用緩存的一些數據,這裡在 inserted 的時候就把當前節點的父節點和替換成的 dom 節點(一個注釋節點),以及節點是否移出去的狀態都記錄在外部的一個 map 中,這樣可以在其他的聲明周期函數中使用,可以避免重複計算。
但是React / Vue的實現都有類似的通病:
需要通過redux或props管理數據,可這對於一個UI組件來說過於臃腫了。React官方也意識到構建脫離於父組件的組件挺麻煩的,於是在v16版本推了一個叫「Portal 」的功能。而Vue3也是借鑑並吸納了優秀插件,將Portal作為內置組件了。
1.3 傳送門Portal方案React / Vue的第二套方案都是基於操作虛擬dom:
「定義一套組件,將組件內的 vnode/ReactDOM 轉移到另外一個組件中去,然後各自渲染。」
2.React的PortalReact Portal之所以叫Portal,因為做的就是和「傳送門」一樣的事情:render到一個組件裡面去,實際改變的是網頁上另一處的DOM結構。
ReactDOM.createPortal(child, container)第一個參數(child)是任何可渲染的 React 子元素,例如一個元素,字符串或碎片。第二個參數(container)則是一個 DOM 元素。在v16中,使用Portal創建Dialog組件簡單多了,不需要牽扯到componentDidMount、componentDidUpdate,也不用調用API清理Portal,關鍵代碼在 render 中,像下面這樣就行:
當然,我們作為一個React Hooks選手,不騷一下咋行。
2.1 熱門組件庫Ant Design中的實現原本是想從Ant Design庫中一窺究竟,卻發現事情並不簡單。。
前後尋址了三個庫/地方,才發現實現的關鍵:
import Dialog from 'rc-dialog';import Portal from 'rc-util/lib/PortalWrapper';import Portal from './Portal';具體實現也算如我所料:
render裡用了ReactDOM.createPortal`
**這也是為什麼多數Modal組件不會提供篡改整體樣式的API,只能通過全局重置樣式。`
2.1 React Hooks版彈窗:useModal步驟一:創建一個Modal組件步驟二:自定義useModal很好理解,不懂的建議轉行寫Vue。
步驟三:使用它3. Vue3的PortalVue雖說是借鑑,但使用方式可容易多了。
在上面的示例中,該<Modal />組件將在id=portal-target的容器中渲染,即使它位於OtherComponent組件內。
這,這...這也太香了吧。進一步的用法如下:
然後我再去找了下Vue3的源碼實現:在packages/runtime-core/src/components/Portal.ts目錄中:
重要的解釋,都在上述注釋中了,臨時看的,說得不對的謝謝指正。
其中:createComment是Vue對DOM.createComment的進一步封裝。
結語&參考這篇算是自己半夜無聊折騰出來的,原定計劃是一篇寫三種組件,但彈窗類的實現比較有意思。
這個系列我會看著寫,不出意外下一篇就是講Steps步驟條和Transfer穿梭框的實現(當然,太難了就忽悠一下,嘿嘿。)
「參考文章:」
❝《Building a simple and reusable modal with React hooks and portals》《Portal – a new feature in Vue 3》覺得本文對你有幫助?請分享給更多人
關注「前端大全」加星標,提升前端技能
好文章,我在看❤️