深度解析!Vue3 & React Hooks 新UI組件原理:Modal 彈窗

2021-03-02 前端大全

(給前端大全加星標,提升前端技能)

作者:前端勸退師 公號 / 前端勸退師

前言

在某個月黑風高的晚上...沒劇刷的我無意想起以前處理的一些彈窗的坑。

然後又無意間刷到「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的Portal

React 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的Portal

Vue雖說是借鑑,但使用方式可容易多了。

在上面的示例中,該<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》

覺得本文對你有幫助?請分享給更多人

關注「前端大全」加星標,提升前端技能

好文章,我在看❤️

相關焦點

  • 精通react/vue組件設計之配合React Portals實現一個(Drawer)組件
    通過組件的設計過程,大家會接觸到一個完成健壯的組件設計思路和方法,也能在實現組件的過程逐漸對react/vue的高級知識和技巧有更深的理解和掌握,並且在企業實際工作做遊刃有餘.作為數據驅動的領導者react/vue等MVVM框架的出現,幫我們減少了工作中大量的冗餘代碼, 一切皆組件的思想深得人心.
  • 《精通react/vue組件設計》之快速實現一個可定製的進度條組件
    每日一學: 組件設計三原則高內聚, 低耦合(尤其是vue/react組件中, 降低組件之間的耦合尤為重要)組件邊界劃分清晰(每一個組件都有自己清晰的邊界劃分)單一職責(props屬性)可以很快的實現多個不同的表現和重用.我將會使用react帶大家實現這個進度條組件, 大家不用擔心技術棧不一樣,因為react實現的組件可以很快套用於vue項目中, 所以說底層原理非常重要.
  • 《精通react/vue組件設計》之實現一個健壯的警告提示(Alert)組件
    正文在開始組件設計之前希望大家對css3和js有一定的基礎,並了解基本的react/vue語法.我們先看看實現後的組件效果:1. 組件設計思路按照之前筆者總結的組件設計原則,我們第一步是要確認需求.選手來說,如果沒用typescript,建議大家都用PropTypes, 它是react內置的類型檢測工具,我們可以直接在項目中導入. vue有自帶的屬性檢測方式,這裡就不一一介紹了.
  • 【Hooks】:[組]Awesome React Hooks
    1.3. What Are Hooks, Exactly?  1.4. Show Me Some Code!2. What's going to happen to render props?3. Migrating from lifecycle methods to hooks  3.1. From componentDidMount to useMount  3.2.
  • 精通react/vue組件設計之實現一個Tag(標籤)和Empty(空狀態)組件
    "辛勤勞動",而是要根據已有前端的開發經驗,總結出一套自己的高效開發的方法.作為數據驅動的領導者react/vue等MVVM框架的出現,幫我們減少了工作中大量的冗餘代碼, 一切皆組件的思想深得人心.所以, 為了讓工程師們有更多的時間去考慮業務和產品迭代,我們不得不掌握高質量組件設計的思路和方法.所以筆者將花時間去總結各種業務場景下的組件的設計思路和方法,並用原生框架的語法去實現各種常用組件的開發,希望等讓前端新手或者有一定工作經驗的朋友能有所收穫
  • 精通React/Vue系列之帶你實現一個功能強大的通知提醒框
    比如Modal組件,我們一般這樣來調用:<Modal title="xui基礎彈窗" centered mask={false} visible={false}><p>我是彈窗內容</p><p>我是彈窗內容</p>
  • 基於Vue實現一個有點意思的拼拼樂小遊戲
    技術棧如下:vue-cli4 基於vue的腳手架Xuery 筆者基於原生js二次封裝的dom庫vue mvvm庫因為該應用屬於H5遊戲,為了清亮化筆者沒有採用第三方ui庫, 如果大家想採用基於vue的第三方移動端ui庫,筆者推薦如下:Mint 餓了麼推出的移動端ui庫NutUI 一套京東風格的移動端組件庫muse-ui 基於MaterialUI風格的移動端UI組件cube-ui
  • Vue 項目中哪些問題戳中你的痛點?你又是如何解決的?(更新中)
    其他頁面進入列表頁刷洗數據的實踐css的scoped私有作用域和深度選擇器hiper打開速度測試vue數據的兩種獲取方式+骨架屏自定義組件(父子組件)的雙向數據綁定路由的拆分管理mixins混入簡化常見操作打包之後文件、圖片、背景圖資源不存在或者路徑錯誤的問題vue插件的開發、發布到github、設置展示地址、發布npm包===========================這是華麗麗的分割線~~=
  • 如何寫一個vue組件專題及常見問題 - CSDN
    <child-component :prop1="var1" :prop2="var2" :prop="var3" ...父組件把對象傳入子組件,是實現雙向綁定的hack方式,但不推薦,vue 3.0可能就要對這種方式說拜拜了,為了以後代碼好改,還是不要用這種方式實現雙向綁定<child-component v-model="text" :setting="{color:'bule'}" />// 子組件內部讀取配置,通過擴展運算符替換掉默認配置
  • GitHub上star超1.2k的vue表格組件,功能太多又實用
    組件名稱:vxe-table項目地址:Github:https://github.com/xuliangzhan/vxe-table>碼云:https://gitee.com/xuliangzhan_admin/vxe-table一個基於 vue 的表格組件,支持增刪改查、虛擬滾動、懶加載、快捷菜單、數據校驗、樹形結構、列印導出、表單渲染、數據分頁、模態窗口、自定義模板、賊靈活的配置項、豐富的擴展插件等面向現代瀏覽器
  • 【Hooks】:[組]Declarative & Imperative
    Declarative vs Imperative Programming3. React Hooks are a More Accurate Implementation of the React Mental Model 3.1. Using React's initial class-based implementation of state and effects 3.2.
  • React 靈魂 23 問,你能答對幾個?
    :2、聊聊 react@16.4 + 的生命周期相關連接:React 生命周期 我對 React v16.4 生命周期的理解3、useEffect(fn, []) 和 componentDidMount 有什麼差異?useEffect 會捕獲 props 和 state。
  • 一張圖教你快速玩轉vue-cli3
    styleLibraryName": "theme-chalk"}]]}此時即可按需引入element組件,優化項目體積了。>"register-service-worker": "^1.6.2","serve": "^11.0.2","vue": "^2.6.10","vue-router": "^3.0.3","vuex": "^3.0.1
  • 準備將您的Vue應用遷移到Vue 3
    例如:// helper/filter.jsexportfunctiontoCurrency (value) {return`$${value.toFixed(2)}`}然後,您可以將輔助函數導入到需要使用它的組件中。例如:// price-component.vueimport { toCurrency } from'.
  • vue 渲染函數插槽專題及常見問題 - CSDN
    插槽是Vue組件的一種機制,它允許你以一種不同於嚴格的父子關係的方式組合組件。插槽為你提供了一個將內容放置到新位置或使組件更通用的出口。從一個簡單的例子開始:// frame.vue這個組件最外層是一個div。假設div的存在是為了圍繞其內容創建一個樣式框架。這個組件可以通用地用於將框架包圍在wq你想要的任何內容上,來看看它是怎麼用的。
  • 尤雨溪在Vue3.0 Beta直播裡聊到了這些…
    等於是:既有react的靈活性,又有基於模板的性能保證。要點 2: 事件監聽緩存:cacheHandlers很多時候,我們並不需要 vue提供的所有功能,在 vue 2 並沒有方式排除掉,但是 3.0 都可能做成了按需引入。5. Composition API
  • 【Vue原理】Props - 源碼版
    好的,回到正題,Props請你在讀這篇之前,先去看看我的白話版【Vue原理】Props - 白話版在上面這篇文章中,也已經清楚地解決了一個問題父組件 如何 把數據 當做 props 傳給子組件所以今天,我們只需記錄 Props 的處理流程源碼即可在創建Vue實例的過程中,會調用 initState
  • 【Hooks】:[組]How to useReducer in React
    React's useReducer HookPart 3: What about useState? Can’t we use that instead?Part 1: What is a reducer in JavaScript?
  • 使用 Vue.js 和 Semantic-UI 做一個簡單的願望清單
    二、Vue.js介紹官方文檔傳送門:https://cn.vuejs.org/v2/guide/installation.htmlVue (讀音 /vjuː/,類似於 view) 是一套用於構建用戶界面的漸進式框架。與其它大型框架不同的是,Vue 被設計為可以自底向上逐層應用。
  • 10個簡單的技巧讓你的 vue.js 代碼更優雅 - 酷扯兒
    本文轉載自【微信公眾號:前端人,ID:FrontendPeople】,經微信公眾號授權轉載,如需轉載原文作者聯繫前言作為深度代碼潔癖,我們都希望能寫出簡單高效的代碼,讓我們的代碼看起來更加優雅,讓我們拋棄繁雜的代碼,一起開啟簡單的旅程~~slots 新語法向 3.0 看齊使用帶有「#」的新命名插槽縮寫語法,在Vue 2.6.0