使用 ReactDom.createPortal 开发一个自定义Modal弹窗

6,487 阅读1分钟

前言

我们在开发组件的时候,很自然的会把组件挂载在父组件上。但是像Modal这类组件,放在具体的DOM中不太合适,应该放在body下比较合适。

那么有没有方法将组件直接挂载到body下呢?答案是有的。 我们可以使用ReactDom.createPortal方法。 Portal 有一个很形象的名称:传送门。就是把组件挂载到任意dom上。

代码示例

如果没有安装react-dom,需要先安装:

yarn add react-dom

我们创建一个Dialog,包括简易内容和样式:

src/portal/Dialog.tsx 内容如下:

import React from 'react'
import './index.css'

export default function Dialog (props: any) {
  return (
    props.visible && (
      <div className='container'>
        <div className='content'>{props.content}</div>
        <div
          className='btn'
          onClick={() => {
            props.close()
          }}
        >
          关闭
        </div>
      </div>
    )
  )
}

src/portal/index.css 如下:

.container {
  height: 200px;
  width: 200px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  background: #ddd;
}
.content {
  padding: 20px;
}
.btn {
  position: absolute;
  padding-bottom: 10px;
  bottom: 0;
  text-align: center;
  width: 100%;
  cursor:default;
}

接下来是createPortal的使用:

createPortal.tsx:

import React, { useState } from 'react'
import { createPortal } from 'react-dom'
import Dialog from './Dialog'

const Portal = (props: any) => {
  const [content, setContent] = useState(new Date().toString())
  const [dialogVisible, setDialogVisible] = useState(false)

  const div = document.createElement('div')
  document.body.appendChild(div)
  console.log('render')

  return (
    <div>
      {createPortal(
        <Dialog
          visible={dialogVisible}
          content={content}
          close={() => {
            setDialogVisible(false)
          }}
        >
          {content}
        </Dialog>,
        div
      )}
      <button
        onClick={() => {
          setContent(new Date().toString())
          setDialogVisible(true)
        }}
      >
        弹出消息
      </button>
    </div>
  )
}

export default Portal

createPortal的具体描述如下:

  • 方法

createPortal(child, container)

  • 参数

-- child 是任何可渲染的 Rax 子元素,例如一个元素,字符串或 Fragment

-- container 是一个 DOM 元素。

在App.tsx中引用 Portal

import React from 'react'
import './App.css'
import Portal from './portal/CreatePortal'

function App () {
  return (
    <div className='App'>
      <header className='App-header'>
        <Portal></Portal>
      </header>
    </div>
  )
}

export default App

代码效果

页面中只有一个按钮:

image.png

点击以后:

image.png

大概的示例就写好了。如果需要丰富的,可自行实现。