---
# System prepended metadata

title: React(MRWR)第 14 節：Creating Protals with ReactDOM
tags: [React, Udemy]

---

# React(MRWR)第 14 節：Creating Protals with ReactDOM

> Udemy課程：[Modern React with Redux [2023 Update]](https://www.udemy.com/course/react-redux/)

`20240131Wed.~20240202Fri.`
:::danger
14-240 createPortal
:::

## 14-236. Modal Component Overview
Modal-互動視窗。
這章節要先做一個button：
![2024-01-31 13-21-11 的螢幕擷圖](https://hackmd.io/_uploads/Hyl1tIPc6.png)

點擊之後，會跳出一個互動視窗，他會置中在畫面。而互動視窗的後方會有一個較暗的背景，若使用者想關掉這個互動視窗，可以透過點擊互動視窗上的某個按鈕，或者直接點擊互動式創外的背景：
![2024-01-31 13-21-50 的螢幕擷圖](https://hackmd.io/_uploads/Sk0gtUD5p.png)

實作步驟：
![2024-02-01 08-54-53 的螢幕擷圖](https://hackmd.io/_uploads/Syo0awdqa.png)


****

## 14-237. Toggling Visibility
按鈕按下去會顯示互動視窗，也可以關掉互動視窗，代表內容有所變動，所以我們會需要用到state！

![2024-02-01 09-45-31 的螢幕擷圖](https://hackmd.io/_uploads/BJ_ZuOdcT.png)

這裡會先設置一個按鈕，但問題來了，我們應該用Modal元件來顯示呢？還是用他們的parent來顯示這按鈕？以及這個變換的state又要定義在哪裡呢？
![2024-02-01 09-47-24 的螢幕擷圖](https://hackmd.io/_uploads/rkZLO_d9p.png)

所以來列出兩種想法來看看，到底怎樣合適。
第一種，無論按鈕或是state，全部設在Modal元件上。
![2024-02-01 10-01-25 的螢幕擷圖](https://hackmd.io/_uploads/Bk4aiu_qp.png)


不過，我們可能會希望這個Modal不一定是在使用者按下按鈕才出現，我們也會希望maybe使用者訪問我們頁面後的10秒，跳出這個互動視窗，讓使用者註冊之類的，所以第一種大概就不合適了，來看看第二種！

![2024-02-01 10-08-58 的螢幕擷圖](https://hackmd.io/_uploads/B1dPpduq6.png)

他的想法如下所示：（此處的parent其實就是指ModalPage）
![image](https://hackmd.io/_uploads/BJ2SRd_9a.png)

****

## 14-238. At First Glance, Easy!
老師在一開始便提到顯示互動視窗這件事情，一開始頗簡單的，但事實上我們會遇到以下困難點需要克服，這將會在後面幾堂課來處理
![2024-02-01 10-43-56 的螢幕擷圖](https://hackmd.io/_uploads/HJppSKuqp.png)

初版實作：
![image](https://hackmd.io/_uploads/ByNI_Y_9p.png)

****

## 14-239. We're Lucky it Works At All!
![2024-02-01 10-57-30 的螢幕擷圖](https://hackmd.io/_uploads/S1no_Fu9T.png)

Position的屬性：（預設為static）
![2024-02-01 10-58-07 的螢幕擷圖](https://hackmd.io/_uploads/rJGC_Y_qp.png)

Absolute規則：
![2024-02-01 10-59-12 的螢幕擷圖](https://hackmd.io/_uploads/rkl7KY_56.png)

問題來了，上圖中的灰色div跟藍色div會呈現在什麼位置呢？
答案如下所示：
![image](https://hackmd.io/_uploads/ByoGiKu9T.png)

再來看看inset-0的規則：
![2024-02-01 11-07-12 的螢幕擷圖](https://hackmd.io/_uploads/H1aXsKOqT.png)

問題又來了，上圖中的綠色div跟藍色div會呈現在什麼位置呢？
答案如下所示：
![image](https://hackmd.io/_uploads/S1EnnKu9T.png)
![image](https://hackmd.io/_uploads/rJRnnKdqp.png)

而從上一章節完成的code來說，會成功的原因是因為很剛好的沒有其他parent擁有其他的position屬性，所以整個灰色背景就能況章整個html document。
![2024-02-01 11-18-04 的螢幕擷圖](https://hackmd.io/_uploads/S1_n6Kdca.png)

![2024-02-01 11-19-29 的螢幕擷圖](https://hackmd.io/_uploads/HJB0pF_56.png)

也就是說只有我們的Modal元件外的任一parent，若擁有任一position屬性（預設static之外的），都會讓灰色背景無法填滿整個html document。

****

## 14-240. Fixing the Modal with Portals
![2024-02-01 11-23-56 的螢幕擷圖](https://hackmd.io/_uploads/Sy2kk9_9T.png)

為了保證之後即便加了position在Modal元件的外層，也可以不破壞整個灰色div覆蓋整個html document，我們這裡會用到一個React的特殊功能，叫做"Portals"。

> 官方文件：[createPortal](https://react.dev/reference/react-dom/createPortal)

Portals會告訴React去做稍微不一樣的事情。他可以讓我們的Modal元件跑到html document的其他地方：
![2024-02-01 11-27-17 的螢幕擷圖](https://hackmd.io/_uploads/H1X-g5_5a.png)

那為什麼這樣移動後，就不會出問題了呢？因為...
![2024-02-01 11-29-37 的螢幕擷圖](https://hackmd.io/_uploads/SkCrlqd9a.png)

所以該怎麼實作呢？接下來，我們將不會只是在元件中return純JSX，，而是會呼叫一個函式名為"ReactDOM.createPortal()"，而Portals將會告訴React將我們的html渲染至其他DOM裡面。

"ReactDOM.createPortal()"接收兩個參數，第一個參數為我們要render的html，第二個參數則是我們想把這個html渲染到「index.html file」中的什麼地方去？

:::warning
[注意！！]
* 要記得先到index.html中，先建立一個div叫做`<div class="modal-container"></div>`
* 要使用ReactDOM，要記得先import： `import ReactDOM from "react"`
:::
![2024-02-01 11-31-01 的螢幕擷圖](https://hackmd.io/_uploads/BkDtg9u96.png)

:::warning
目前使用ceratePortal的方式大致上有以下兩種：
（這一堂課底下剛好有人詢問）

```javascript!
import { createPortal } from "react-dom"
 
...
 
return createPortal(
```
vs:

```javascript!
import ReactDOM from 'react-dom';
 
...
 
return ReactDOM.createPortal(
```
:::

****

## 14-241. Closing the Modal
再來要實作把這個互動視窗給關掉的功能。首先，先來完成「點擊灰色背景即可關掉」的功能。

實作想法：
![2024-02-01 12-14-36 的螢幕擷圖](https://hackmd.io/_uploads/SkGjhc_c6.png)

實作整個過程：
![image](https://hackmd.io/_uploads/Hyw9Rcdcp.png)

****

## 14-242. Customizing the Modal
![image](https://hackmd.io/_uploads/HykFy6Fca.png)

****

## 14-244. One Small Bug
產生假文章的網站：[Lipsum](https://www.lipsum.com/)

****

## 14-245. Modal Wrapup
* 問題1：
加上一對假文章後，我們網頁出現了scroll，此時若打開互動視窗會發現往下滑時，會......
![2024-02-02 09-31-04 的螢幕擷圖](https://hackmd.io/_uploads/HyUyITF9p.png)

* 解決想法：
當打開互動視窗時，在body加上`overflow: hidden`，讓頁面無法滑動，而當關掉互動視窗時，就把這個樣式給移除。

* 實作辦法：
    利用`useEffect` hook，當Modal元件被創造（或說被安裝）後，就會觸發`useEffect`幫body加上`overflow: hidden`，我們在第二個參數設置`[]`，這樣他只有第一次render時才會被觸發

    另外在useEffect中return，這樣當Modal元件被移除銷毀（或說被卸載），那麼就會return，return什麼呢？就是把`overflow: hidden`從body上移除。
    
    //Modal
    ```javascript!
    import  ReactDOM  from "react-dom";
    import { useEffect, useReducer } from "react"; 

    function Modal({ onClose, children, actionBar}){
        //只有在Modal顯現出來useEffect才會作用
        useEffect(() => {
            document.body.classList.add("overflow-hidden");

            return () => {
                document.body.classList.remove("overflow-hidden");
            }
        }, [])

        return ReactDOM.createPortal(
            <div>
                <div onClick={onClose} className="absolute inset-0 bg-gray-300 opacity-80"></div>
                <div className="absolute inset-40 p-10 bg-white">
                    <div className="flex flex-col justify-between h-full">
                        {children}
                        <div className="flex justify-end">
                            {actionBar}
                        </div>
                    </div>
                </div>
            </div>,

            document.querySelector(".modal-container")
        );
    }

    export default Modal;
    ```
    
    ****
    
* 問題2：
    假如button在網頁最下方，所以使用者必須先 scroll down到底下，此時若點擊按鈕打開互動視窗時，會發生...
    互動視窗是打開了，也加上了`overflow: hidden`在body上，所以也無法滾動，但是互動視窗會緊貼著網頁最上方，所以我們無法看到完整的互動視窗，當然灰色背景也被卡一半。
    ![2024-02-02 10-17-16 的螢幕擷圖](https://hackmd.io/_uploads/ByRhlCKca.png)

* 解決想法：
    這是因為我們有對Modal下absolute，所以他會黏在body最上方及最左側，所以不應該用absolute
    
* 實作辦法：
    將absolute用fixed替代即可。
    > fixed參考：[position - 金魚都能懂的CSS必學屬性](https://ithelp.ithome.com.tw/articles/10253500)
    > 
    > 「fixed 固定定位」的效果是將設定的物件，將其參考空間設定為「視窗」，也就是說當我對一個物件設定了 position: fixed; 之後，該物件的參考空間就直接對視窗的範圍了，需要特別注意的是 fixed 定位與 absolute ，一樣都會自己獨立一層，所以這兩者非常容易被不少人誤會是相同的，但其實特性的細節差異很多阿！ 