👊 【React Day 1】Toast 组件实现
🅰️ About
今天花了一天时间在看 React
相关的内容,配合着 ChatGPT 以及文档试着写了一个 Toast 组件
so,大概就是这样,一个十分常见的消息提示组件
关于 Framer Motion
,以及 React
的项目结构,我参考了 cali 的 Next.js 博客
https://github.com/CaliCastle/cali.so
Source Code
需要依赖
- framer-motion
- tailwindcss
"use client";
import React, { useState, useEffect } from "react";
import { createRoot } from "react-dom/client";
import { AnimatePresence, motion } from "framer-motion";
import EventEmitter from "events";
enum ToastType {
"error" = "#f87171",
"success" = "#34d399",
"info" = "#94a3b8",
"warning" = "#fbbf24",
}
type ToastDefine = {
id?: string;
content?: string;
duration?: number;
type: keyof typeof ToastType | string;
color?: string;
borderColor?: string;
bgColor?: string;
};
class ToastManager {
root: HTMLElement | null;
eventEmitter: EventEmitter;
onDestroy: (() => void) | null = null;
constructor(onMounted: (eventEmitter: EventEmitter) => void, onDestroy: () => void) {
this.eventEmitter = new EventEmitter();
this.root = document.createElement("div");
document.createElement("div");
document.body.appendChild(this.root);
createRoot(this.root).render(
<ToastProvider
onMounted={() => {
onMounted(this.eventEmitter);
}}
destroy={this.destroy}
eventEmitter={this.eventEmitter}
/>,
);
this.onDestroy = onDestroy;
}
destroy = () => {
this.onDestroy && this.onDestroy();
setTimeout(() => {
this.root?.remove();
this.root = null;
}, 200);
};
}
function ToastProvider({
onMounted,
destroy,
eventEmitter,
}: {
onMounted: () => void;
destroy: () => void;
eventEmitter: EventEmitter;
}) {
const [toasts, setToasts] = useState<ToastDefine[]>([]);
const showToastAction = (message: ToastDefine) => {
message.id = String(Math.random());
setToasts(oldToasts => [...oldToasts, message]);
setTimeout(() => {
setToasts(oldToasts => {
if (oldToasts.length === 1) destroy();
return oldToasts.filter(toast => toast.id !== message.id);
});
}, message.duration || 2000);
};
useEffect(() => {
eventEmitter.on("showToast", showToastAction);
if (onMounted) onMounted();
return () => {
eventEmitter.off("showToast", showToastAction);
};
}, []);
return (
<div className="fixed right-[20px] top-[20px] flex flex-col gap-[10px]">
<AnimatePresence>
{toasts.map(toast => (
<Toast key={toast.id} msg={toast} />
))}
</AnimatePresence>
</div>
);
}
const Toast = ({ msg }: { msg: ToastDefine }) => {
const color = getColor(msg.type);
const item: ToastDefine = {
...msg,
color,
borderColor: color,
bgColor: `${color}20`,
};
return (
<motion.div
layout="position"
initial={{ opacity: 0, scale: 0.5, x: -60 }}
animate={{ opacity: 1, scale: 1, x: 0 }}
exit={{ opacity: 0, scale: 0.5, x: -60 }}
transition={{ duration: 0.2 }}
>
<div
className="bg-white rounded-md min-w-[200px] p-[10px] text-[13px] border-solid border"
style={{ backgroundColor: item.bgColor, color: item.color, borderColor: item.borderColor }}
>
{msg.content}
</div>
</motion.div>
);
};
const getColor = (key: string) => {
let color = ToastType[key as keyof typeof ToastType];
if (color) return color;
return key;
};
let toastInstance: ToastManager | null = null;
function show(msg: ToastDefine | string) {
let message: ToastDefine = {
duration: 2000,
type: "info",
};
if (typeof msg === "string")
message = {
content: msg,
...message,
};
else message = { ...message, ...msg };
if (!toastInstance) {
toastInstance = new ToastManager(
eventEmitter => {
eventEmitter.emit("showToast", message);
},
() => {
toastInstance = null;
},
);
} else toastInstance.eventEmitter.emit("showToast", message);
}
export default { show };
调用
import Toast from "@/components/Toast";
Toast.show({
content: "2333",
type: "info",
});
关于 useEffect
也就是说,
useEffect
的执行取决于第二个参数对吗?如果为[]
则只会在挂载和卸载时执行,假如给定一个可变值,那么值的变化就会导致useEffect
执行,是这样理解吗?是的,你理解得完全正确。
useEffect
的执行确实取决于它的第二个参数,也就是依赖数组。
•如果依赖数组为空([]
),那么useEffect
中的副作用函数只会在组件首次渲染(挂载)后和卸载前执行。它等效于componentDidMount
和componentWillUnmount
生命周期方法。
•如果依赖数组包含一些变量,那么useEffect
中的副作用函数将在组件首次渲染后,以及任何依赖项改变时执行。它等效于componentDidMount
和componentDidUpdate
生命周期方法。
•如果不提供依赖数组,那么useEffect
中的副作用函数将在组件首次渲染后和每次更新后(无论何时调用render
)执行。
所以,useEffect
可以根据需要,模拟类组件中的各种生命周期方法。
提出这个问题的缘由是因为起初想通过 useEffect(() => void, [toasts])
来监听 toasts
值的改变(toasts
为空时清除容器div
及 toastInstance
),但是这样也导致了函数 onMounted
的反复执行
wpyfknufit
Muchas gracias. ?Como puedo iniciar sesion?