如何复用Material UI中的Popper触发按钮组件
如何复用Material UI中的Popper触发按钮组件
嘿,你已经把Popper的动态内容搞定了,现在想把触发按钮也做成可复用的,这样不管是筛选、搜索还是其他场景都能直接用对吧?这事儿很简单,咱们只需要把触发按钮里那些可变的部分(比如按钮里的文字、图标、样式)都改成通过props从父组件传入,同时保留原有的Popper状态逻辑就行,不用重复写那些显示隐藏的代码。
我给你改好了可复用的组件,你看看:
import * as React from "react"; import Popper from "@mui/material/Popper"; // 现在这个组件支持自定义触发按钮的所有可变内容啦 export default function ReusablePopper({ triggerContent, // 触发按钮里的内容,比如图标+文字,随便你传什么React节点都行 triggerProps = {}, // 按钮的自定义属性,比如额外的className、disabled状态这些,用户传的会覆盖默认值 children, // 原来的Popper弹出内容,和之前一样用children传 }) { const [anchorEl, setAnchorEl] = React.useState(null); const handleClick = (event) => { setAnchorEl(anchorEl ? null : event.currentTarget); }; const open = Boolean(anchorEl); const id = open ? "simple-popper" : undefined; return ( <div> <button // 合并默认属性和用户传入的props,用户传的会覆盖默认的 {...triggerProps} // 保留必要的aria属性,确保 accessibility aria-describedby={triggerProps["aria-describedby"] ?? id} // 点击事件还是用组件内部的逻辑,不用用户自己写 onClick={handleClick} // 默认样式保留,同时允许用户加自定义的className className={`flex items-center gap-x-2 rounded-lg lg:rounded-xl border border-[#d2d1d3] lg:py-2 lg:pl-3 lg:pr-8 px-3 py-2 ${triggerProps.className || ""}`} // 默认type设为button,避免表单里的默认提交行为 type={triggerProps.type ?? "button"} > {/* 这里渲染用户传进来的按钮内容,比如图标+文字 */} {triggerContent} </button> <Popper id={id} open={open} anchorEl={anchorEl} placement="bottom-start"> {children} </Popper> </div> ); }
怎么用这个组件?举两个场景例子你就懂了
1. 搜索场景的用法
你可以给触发按钮传搜索图标+“搜索”文字,弹出内容放搜索框:
<ReusablePopper triggerContent={ <> {/* 这里可以放你自己的搜索图标,或者用MUI的Icon组件 */} <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> </svg> <span>搜索</span> </> } // 可以加自定义的className,比如给搜索按钮加个特殊的样式 triggerProps={{ className: "bg-gray-50" }} > {/* 这里是Popper弹出的搜索内容 */} <div className="p-3 bg-white shadow-lg rounded-lg w-72"> <input type="text" placeholder="输入关键词搜索" className="border w-full p-2 rounded" /> <div className="mt-2 text-sm text-gray-500">最近搜索:React, MUI Popper</div> </div> </ReusablePopper>
2. 筛选场景的用法
换个图标和文字,就能直接当筛选按钮用:
<ReusablePopper triggerContent={ <> <svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z" /> </svg> <span>筛选</span> </> } triggerProps={{ className: "bg-blue-50 text-blue-700 border-blue-200" }} > {/* 筛选的弹出内容,比如各种筛选选项 */} <div className="p-3 bg-white shadow-lg rounded-lg w-64"> <div className="mb-2"> <label className="block text-sm font-medium">任务状态</label> <select className="w-full border p-2 rounded mt-1"> <option>全部</option> <option>已完成</option> <option>进行中</option> </select> </div> <button className="w-full bg-blue-600 text-white p-2 rounded">应用筛选</button> </div> </ReusablePopper>
要是你想要更灵活的控制?
如果有时候你不想用button当触发元素(比如想用div或者MUI的IconButton),那咱们可以再改得更灵活一点,让父组件直接渲染触发元素,组件只负责Popper的状态管理:
import * as React from "react"; import Popper from "@mui/material/Popper"; export default function SuperReusablePopper({ renderTrigger, // 父组件自己写触发元素的渲染逻辑,组件会把必要的状态传过去 children, }) { const [anchorEl, setAnchorEl] = React.useState(null); const handleClick = (event) => { setAnchorEl(anchorEl ? null : event.currentTarget); }; const open = Boolean(anchorEl); const id = open ? "simple-popper" : undefined; return ( <div> {/* 把点击事件、打开状态、id都传给父组件,让父组件自己控制触发元素 */} {renderTrigger({ handleClick, open, id })} <Popper id={id} open={open} anchorEl={anchorEl} placement="bottom-start"> {children} </Popper> </div> ); }
用的时候就可以完全自定义触发元素了,比如:
<SuperReusablePopper renderTrigger={({ handleClick, open, id }) => ( <div aria-describedby={id} onClick={handleClick} className={`flex items-center gap-2 p-2 rounded-lg cursor-pointer ${open ? "bg-gray-100" : ""}`} > <span>用户中心</span> {/* 打开的时候图标旋转,这个逻辑也可以自己控制 */} <svg className={`w-4 h-4 transition-transform ${open ? "rotate-180" : ""}`} fill="none" stroke="currentColor" viewBox="0 0 24 24"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" /> </svg> </div> )} > <div className="p-3 bg-white shadow-lg rounded-lg"> <div className="p-2 hover:bg-gray-100 cursor-pointer">我的资料</div> <div className="p-2 hover:bg-gray-100 cursor-pointer">账户设置</div> <div className="p-2 hover:bg-gray-100 cursor-pointer">退出登录</div> </div> </SuperReusablePopper>
核心思路就是把可变的部分交给父组件自定义,把不变的Popper状态逻辑留在复用组件里,这样不管什么场景,你都只需要写一次Popper的显示隐藏逻辑,触发按钮想怎么改就怎么改,完全不用动核心代码。
备注:内容来源于stack exchange,提问作者Sina Sakhaei
相关产品推荐
相关产品推荐

