序
日常开发中有全局 message 组件的需求.
- 全局使用
- 样式隔离(避免其他组件样式影响
message组件样式)
Context
Context 全局提供 message 方法
Portal
Portal 将子节点渲染到 body 避免与父节点发生样式冲突
实现
MessageContext
// 省略引用
// message 类型
type MessageType = 'success' | 'error' | 'info'
// message 配置
interface MessageOption {
type: MessageType
duration: number
}
// success... 方法
type MessageMethod = (
content: string,
option?: Partial<Omit<MessageOption, 'type'>>
) => void
// context props
interface MessageProps {
success: MessageMethod
error: MessageMethod
info: MessageMethod
}
const MessageContext = React.createContext<MessageProps>({
success: () => {},
error: () => {},
info: () => {},
})
// 方便其他组件使用
export function useMessage() {
return React.useContext(MessageContext)
}
MessageContent
message 显示组件,根据需求调整
// message 类型图标
const MessageTypeIcons: { [props in MessageType]: ReactNode } = {
success: <IcBaselineCheckCircle className="w-6 h-6 text-teal-600" />,
error: <IcBaselineError className="w-6 h-6 text-yellow-500" />,
info: <IcBaselineInfo className="w-6 h-6 text-gray-500" />,
}
interface Props {
message: string
type: MessageType
}
const MessageContent: React.FC<Props> = ({ message, type }) => {
return (
<div
role="alert"
className="fixed top-8 z-50 w-fit min-w-[200px] left-4 right-4 ml-auto mr-auto rounded-xl border bg-white border-gray-100 p-4 shadow-xl dark:border-gray-800 dark:bg-gray-900"
>
<div className="flex items-start gap-4">
<span>{MessageTypeIcons[type]}</span>
<div className="flex-1">
<strong className="block text-center font-medium text-gray-900 dark:text-white">
{message}
</strong>
</div>
</div>
</div>
)
}
安利图标网站 Icônes (icones.js.org)
这里使用 Tailwind CSS 实现样式,核心思想是 fixed 定位,居中显示,根据个人喜好实现就好.
.message{
position: fixed;
width: fit-content;
top: 2vh;
left: 1rem;
right: 1rem;
margin-left: auto;
margin-right: auto;
}
MessageProvider
const DEFALUT_MESSAGE_OPTION: MessageOption = {
type: 'info',
duration: 2000,
}
export const MessageProvider: React.FC<PropsWithChildren> = ({ children }) => {
const [showModal, setShowModal] = useState(false)
const [msg, setMsg] = useState('')
const [messageOption, setMessageOption] = useState<MessageOption>(DEFALUT_MESSAGE_OPTION)
useEffect(() => {
if (showModal) {
setTimeout(() => {
setShowModal(false)
}, messageOption.duration)
}
}, [messageOption.duration, showModal])
// 对应type的方法
const messageMethod = useCallback(
(type: MessageType) =>
(content: string, option?: Partial<Omit<MessageOption, 'type'>>) => {
setShowModal(true)
setMsg(content)
setMessageOption((opt) => ({ ...opt, ...option, type }))
},
[]
)
return (
<MessageContext.Provider
value={{
success: messageMethod('success'),
error: messageMethod('error'),
info: messageMethod('info'),
}}
>
{showModal &&
ReactDOM.createPortal(
<MessageContent message={msg} type={messageOption.type} />,
document.body
)}
{children}
</MessageContext.Provider>
)
}
使用
MessageProvider 包裹 需要使用的组件
<MessageProvider>
<App />
</MessageProvider>
useMessage 调用方法
const MessagePagae: React.FC = () => {
const message = useMessage()
return (
<div>
<button onClick={() => message.success('success')}>
success
</button>
<button onClick={() => message.error('error')}>
error
</button>
<button onClick={() => message.info('info')}>
info
</button>
</div>
)
}
终
这样就实现了一个 message 组件基本需求,还有许多地方可以完善
- 美化样式,添加动画
- 添加配置和手动关闭方法
- 优化同时显示多个消息
- ...