0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactDOM portalとは?

Posted at

RactDOM portal

Portal機能は、親コンポーネントのDOM階層構造の外側にあるDOMノードに子をレンダリングする機能を提供します。 普通は自分を囲んでいる親の下で描かれます。しかし、視覚的に子を飛び出して出るように見せなければならない場合があります。

Portal 使用前

Dialog.jsx
import React from 'react'
import { useState } from 'react'

export default function Dialog(props) {
    const [isOpen, setIsOpen] = useState(false);
    return (
    <>
        <button
        style={{
            position: 'absolute',
            left: 100,
        }}
        onClick={()=> setIsOpen(true)}>Open</button>
        
        {isOpen && <div
        style={{
            position: "absolute",
            zIndex: 99,
            top: "50%",
            left: "50%",
            transform: "translate(-50%, -50%)",
            border: "1px solid black",
            padding: 24,
            backgroundColor: "white",
        }}
        >
            { typeof props.title === "string" ? (
            <h1>{props.title}</h1> 
            ) :  (props.title)} 
            
            { typeof props.description === "string" ? (
            <h1>{props.description}</h1> 
            ) :  (props.description)} 

            { typeof props.button === "string" ? (
            <button style={{backgroundColor:"red", color: "wthie"}}onClick={()=> setIsOpen(false)}>{props.button}</button>
            ) :  (
                <div onClick={()=>setIsOpen(false)}>
                {props.button}
                </div>
            )} 
            </div>
        }
    {isOpen && <div style={{
        position: "fixed",
        top: 0,
        left: 0,
        bottom: 0,
        right: 0,
        backgroundColor: "lightgray"
    }}/>}
    </>
    );
}

Dialog.jsxは isOpen State値によって、ボタンの表示状態が変わるし、
Propsを受け取って、dialogに出力する内容を決めます。

ThankyouDialog.jsx
import React from 'react'
import Dialog from './Dialog'

export default function ThankyouDialog() {
  return (
    <Dialog 
        title={<h1 style={{color: `red`}}>Sorry</h1>}
        description="Sorry My Fault!!"
        button={<button style={{backgroundColor: "red", color:"white"}}>close</button>}
        />
  )
}

ThankyouDialog.jsxはDialog.jsxにDialogに出力する内容をPropsにて渡します。

Example.jsx
import React from 'react'
import { createPortal } from 'react-dom'
import ThankyouDialog from './ThankyouDialog'

export default function Example() {
    return (
        <div>
            <ThankyouDialog />
            <div style={{ position: "absolute "}}>
                <button>hahah</button>
            </div>
        </div>
    )
}

Example.jsxはThankyouDialogコンポーネントとhahahaボタンを出力しています。

下は、openボタンをクリックした画面です。
ダイアログがz-index基準で既存のものをすべて上書きしなければならないが、後ろにあるものが上位を占めるためhahaが見えてしまいます。
image.png

hahahaを非表示にするには、Example.jsxのThankyouDialogをhahaha Divの次に配置させれば、hahahaボタンが非表示になります。
image.png
上記の場合は、子供たちが同じ親の下にいるため、位置を変えることで被せることができますが、
もしレイヤーがあるコンポーネントの中に中に入っていると、レイヤーが順次持っているUIスタックを離れることが不可能になります。

Portal 使用方法

index.htmlにidをportalを持つdivを追加します。

index.html
  // 省略
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div> 
    <div id="portal"></div> 
  // 省略
  </body>
</html>
Example.jsx
import React from 'react'
import { createPortal } from 'react-dom'
import ThankyouDialog from './ThankyouDialog'

const Portal = (props) => {
    return createPortal(props.children, document.getElementById('portal'));
}

export default function Example() {
    return (
        <div>
            <Portal>
                <ThankyouDialog />
            </Portal>
            <div style={{ position: "absolute "}}>
                <button>hahah</button>
            </div>
        </div>
    )
}

createPortal(child, container)を使って、外側にあるDOMノードに子をレンダリングします。
上記だと、が子ともになり、index.htmlのportal divがcontainerになります。
image.png

開発者モードで確認したElement!ThankyouDialogがportal divにレンダリングされていることがわかります!
image.png

portal event

Example.jsx
import React from 'react'
import { createPortal } from 'react-dom'
import ThankyouDialog from './ThankyouDialog'

const Portal = (props) => {
    return createPortal(props.children, document.getElementById('portal'));
}

export default function Example() {
    return (
        <div onClick={() => console.log('div')}>
            <Portal>
                <ThankyouDialog />
            </Portal>
            <div style={{ position: "absolute "}}>
                <button>hahah</button>
            </div>
        </div>
    )
}

divにeventとしてonClickを定義しています。
hahahaを押下すると、もちろんdivログが出力されますが、ThankyouDialogはindex.htmlのrootではない、portalタグに入っているため、portalでEventが起きてもrootには伝わると思われますが
Reactでは意図的にPortalにあるElementでもそこで発生するEventはrootにも伝えています。

image.png

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?