来源:小满zs React教程学习笔记。
Message
Message.tsx
import { type FC } from "react";
import { createRoot, type Root } from "react-dom/client";
import "./Message.css";
export const Message: FC = () => {
return <div>提示信息</div>;
};
interface MessageItem {
MessageContainer: HTMLElement;
root: Root;
}
const queue: MessageItem[] = [];
window.onShow = () => {
// 1. 创建消息容器
const messageContainer = document.createElement("div");
messageContainer.className = "message-container";
messageContainer.style.top = `${queue.length * 50}px`;
document.body.appendChild(messageContainer);
// 2. 容器关联Message组件 将容器注册为根节点
const root = createRoot(messageContainer);
root.render(<Message />);
queue.push({
MessageContainer: messageContainer,
root: root,
});
// 3. 设置定时器 定时移除容器
setTimeout(() => {
const item = queue.find(
(item) => item.MessageContainer === messageContainer
)!;
item.root.unmount();
document.body.removeChild(messageContainer);
queue.splice(queue.indexOf(item), 1);
}, 2000);
};
// ts 声明扩充
declare global {
interface Window {
onShow: () => void;
}
}
Message.css
.message-container {
width: 200px;
height: 40px;
color: #18181d;
line-height: 40px;
text-align: center;
border-radius: 10px;
position: fixed;
top: 10px;
left: 50%;
transform: translateX(-50%);
background-color: #eee;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
main.ts
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import "./components/Message/Message.tsx";
import "./components/Modal/Modal.tsx";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);
App.tsx
import "./App.css";
function App() {
return (
<>
<div className="app-container">
<button onClick={() => window.onShow()}>显示消息</button>
<button onClick={() => window.onShowModal()}>显示弹窗</button>
</div>
</>
);
}
export default App;
App.css
.app-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
Modal
Modal.tsx
import React, { type FC } from 'react'
import { createRoot } from 'react-dom/client'
import './Modal.css'
export const Modal: FC = () => {
return (
<div className='modal'>
<header className='header'>
<h3>标题</h3>
</header>
<main className='main'>
<p>内容</p>
</main>
<footer className='footer'>
<button>取消</button>
<button>确定</button>
</footer>
</div>
)
}
window.onShowModal = () => {
const modal = document.createElement('div')
modal.className = 'modal'
document.body.appendChild(modal)
const root = createRoot(modal)
root.render(<Modal />)
}
declare global {
interface Window {
onShowModal: () => void
}
}
Modal.css
.modal {
width: 300px;
height: 150px;
border-radius: 10px;
border: 1px solid #ccc;
padding: 10px;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
background-color: #fff;
}
.header {
height: 30px;
border-bottom: 1px solid #ccc;
}
.main {
height: 80px;
}
.footer {
height: 30px;
border-top: 1px solid #ccc;
display: flex;
justify-content: flex-end;
}
createPortal
但是 position: fixed,会有很多问题,在默认的情况下他是根据浏览器视口进行定位的,但是如果父级设置了transform、perspective、filter 或 backdrop-filter 属性非 none 时,他就会相对于父级进行定位,这样就会导致Modal组件定位不准确(他不是一定按照浏览器视口进行定位),所以不推荐使用。
所以此时我们可以使用 createPortal 这个 API 将其指定挂载到某个位置。
createPortal
传参:
children:要渲染的组件
domNode:要渲染到的DOM位置
key?:可选,用于唯一标识要渲染的组件
返回值:
返回一个React元素(即jsx),这个元素可以被React渲染到DOM的任意位置
应用场景:
弹窗
下拉框
全局提示
全局遮罩
全局Loading
修改后的 Modal 为:
Modal.tsx
import { type FC } from "react";
import { createPortal } from "react-dom";
import { createRoot } from "react-dom/client";
import "./Modal.css";
export const Modal: FC = () => {
return createPortal(
<div className="modal">
<header className="header">
<h3>标题</h3>
</header>
<main className="main">
<p>内容</p>
</main>
<footer className="footer">
<button>取消</button>
<button>确定</button>
</footer>
</div>,
document.body
);
};
window.onShowModal = () => {
const modal = document.createElement("div");
modal.className = "modal";
document.body.appendChild(modal);
const root = createRoot(modal);
root.render(<Modal />);
};
declare global {
interface Window {
onShowModal: () => void;
}
}
Modal.css
.modal {
width: 300px;
height: 150px;
border-radius: 10px;
border: 1px solid #ccc;
padding: 10px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
background-color: #fff;
position: absolute;
}
.header {
height: 30px;
border-bottom: 1px solid #ccc;
}
.main {
height: 80px;
}
.footer {
height: 30px;
border-top: 1px solid #ccc;
display: flex;
justify-content: flex-end;
}