我有一个
Button
组件,允许一些道具(如 variant
、active
、size
等)更改其样式。
我想要的是将 Button 组件传递给另一个组件并在那里将其用作普通组件。
function Header() {
return (
<div>
<Button.Link href="/">Log in</Button.Link>
<Button.Link href="/">Sign up</Button.Link>
<Dropdown
Trigger={
<Button variant="outline" size="icon">
<MenuIcon className="size-4" />
</Button>
}
>
<div className="absolute right-0 top-8 h-72 w-64 bg-red-200">Hello</div>
</Dropdown>
</div>
);
}
export default Header;
如您所见,我将
Button
组件传递给 Dropdown
组件。
现在在下拉菜单中,我想使用它,如下所示:(用于解释的虚拟代码)
interface DropdownProps {
Trigger: any;
children: React.ReactNode;
}
function Dropdown({ Trigger, children }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const toggleDropdown = () => {
setIsOpen((current) => !current);
};
useClickOutside(dropdownRef, () => setIsOpen(false));
return (
<div ref={dropdownRef} className="relative">
{<Trigger onClick={toggleDropdown} active={isOpen} />} <-- like this
{isOpen && children}
</div>
);
}
export default Dropdown;
如何实现这个功能?
我做了什么:
我找到了一个使用 React 的解决方案
cloneElement
但对此并不满意(感觉有更好的方法)。
interface DropdownProps {
trigger: React.ReactElement<{
onClick: () => void;
active: boolean;
}>;
children: React.ReactNode;
}
function Dropdown({ trigger, children }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const toggleDropdown = () => {
setIsOpen((current) => !current);
};
useClickOutside(dropdownRef, () => setIsOpen(false));
return (
<div ref={dropdownRef} className="relative">
{cloneElement(trigger, { onClick: toggleDropdown, active: isOpen })}
{isOpen && children}
</div>
);
}
export default Dropdown;
还有更好的吗?
管理 props 并确保更好的类型安全性的更好方法是将 Trigger 定义为直接从 Dropdown 接收必要的 props 和事件的函数组件。这种方法避免了 React.cloneElement,使代码更干净并提供更好的类型安全性。
以下是如何使用 Trigger 函数组件来实现它,该组件从 Dropdown 接收 onClick 和 active 属性。
import React, { useState, useRef } from 'react';
interface DropdownProps {
Trigger: (props: { onClick: () => void; active: boolean }) => JSX.Element;
children: React.ReactNode;
}
function Dropdown({ Trigger, children }: DropdownProps) {
const [isOpen, setIsOpen] = useState(false);
const dropdownRef = useRef<HTMLDivElement>(null);
const toggleDropdown = () => {
setIsOpen((current) => !current);
};
useClickOutside(dropdownRef, () => setIsOpen(false));
return (
<div ref={dropdownRef} className="relative">
<Trigger onClick={toggleDropdown} active={isOpen} />
{isOpen && children}
</div>
);
}
export default Dropdown;
现在在 Header 中,您可以将 Trigger 作为函数传递,并将其与 Button 组件一起使用:
function Header() {
return (
<div>
<Button.Link href="/">Log in</Button.Link>
<Button.Link href="/">Sign up</Button.Link>
<Dropdown
Trigger={({ onClick, active }) => (
<Button variant="outline" size="icon" onClick={onClick} active={active}>
<MenuIcon className="size-4" />
</Button>
)}
>
<div className="absolute right-0 top-8 h-72 w-64 bg-red-200">Hello</div>
</Dropdown>
</div>
);
}
希望对你有帮助。