使用react的createPortal实现全局消息提示组件
如何封装一个全局的消息提示组件
心路历程:
在我的mini-bolg开发过程中,需要封装一个消息提示组件,项目基于nextjs+daisyui(一个基于tailwinds的ui库)开发。在组件封装的过程中,遇到了一些问题,在此记录一下==
项目环境:
1
2
3
4
5
6
7 "next": "14.2.2",
"react": "^18",
"react-dom": "^18",
"daisyui": "^4.10.2",
"uuid": "^9.0.1",
"uuidv4": "^6.2.13"
"tailwindcss": "^3.4.1",
组件需求描述
此组件用于操作后的提示信息,如请求失败后,页面提示错误信息。
用法类似于antd组件库的message,如下
1
2
3
4
5
6
7
8
9
10
11
12
13import {useToast} from "@/app/lib/components/Toast";
const Page() => {
const [toastApi, container] = useToast()
// 调用info类型的提示信息
toast.info(message)
return (
<div>
{container}
// 其他内容
</div>
)
}组件DOM应该渲染在body节点下
组件实现
定义类型文件/components/Toast/types.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21export type ToastStatus = "success" | "error" | "warning" | "info";
export interface INotice {
status: ToastStatus;
text: string;
key: string;
}
export interface ToastProps {
key: string;
text: string;
status: ToastStatus;
}
export interface ToastApi {
info: (text: string, delay?: number) => void;
success: (text: string, delay?: number) => void;
warning: (text: string, delay?: number) => void;
error: (text: string, delay?: number) => void;
}首先在components/Toast/目录下定义一个
Toast.tsx
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import {FC} from "react";
import {ToastProps} from "@/lib/components/Toast/types";
import classNames from "classnames";
const Toast:FC<ToastProps> = (props) => {
const { text, status } = props
const classnames = classNames("alert","animate__fadeInDown", {
"alert-info": status === "info",
"alert-success": status === "success",
"alert-error": status === "error",
"alert-warn": status === "warning",
})
return (
<div className={classnames}>
<span>{text}</span>
</div>
)
}
export default Toast;定义
useToast.tsx
文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51import {ReactPortal, useState} from "react";
import {INotice, ToastApi, ToastStatus} from "@/lib/components/Toast/types";
import {v4} from "uuid";
import {createPortal} from "react-dom";
import Toast from "@/lib/components/Toast/Toast";
const useToast: ()=>[toastApi: ToastApi, container: ReactPortal | false] = () => {
const [notices, setNotices] = useState<INotice[]>([])
const container= notices.length>0 && createPortal(<div className='toast toast-top toast-center'>
{
notices.length > 0 && notices.map((notice:INotice) => (
<Toast key={notice.key} text={notice.text} status={notice.status}></Toast>
))
}
</div>, document.body)
const add = (notice: INotice) => {
setNotices((prevState) => [...prevState, notice])
}
const remove = (key:string) => {
setNotices(prevState => prevState.filter(v=>v.key !== key))
}
const createNotice = (text: string, status: ToastStatus, key: string, delay: number) => {
add({
text,
status,
key
})
setTimeout(() => remove(key), delay)
}
const toastApi = {
warning: (text: string, delay:number=3000) => {
createNotice(text, 'warning', v4(), delay)
},
success: (text: string, delay:number=3000) => {
createNotice(text, 'success', v4(), delay)
},
info: (text: string, delay:number=3000) => {
createNotice(text, 'info', v4(), delay)
},
error: (text: string, delay:number=3000) => {
createNotice(text, 'error', v4(), delay)
}
}
return [toastApi, container]
}
export default useToast最后定义
index.tsx
文件导出需要的模块1
export { default as useToast } from './useToast'
组件封装的核心
- 使用createPortal()方法,像body添加Toast组件
1 | const container= notices.length>0 && createPortal(<div className='toast toast-top toast-center'> |
遇到的问题
- 起初对createPortal的用法理解错了,以为和createRoot,然后掉用render方法是一样的。但其实,render方法是向节点插入一个元素,而createPortal是创造一个react的组件,把这个组件放在react的jsx代码中, 尽管这个组件看起来可能在你的id为app的div之下,但是在渲染的时候react会将他插入,你指定的位置,也就是createPortal的第二个参数。妙啊~
1 | // 返回的一个组件 |
- 标题: 使用react的createPortal实现全局消息提示组件
- 作者: DansRoh
- 创建于 : 2024-04-29 00:00:00
- 更新于 : 2024-06-24 16:20:56
- 链接: https://blog.shinri.me/2024/04/29/23_封装一个react全局消息提示/
- 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论