下拉菜单长期以来一直是网站和应用程序中的重要组成部分。它们是用户交互的默默英雄,通过简单的点击或轻触默默地促进着无数的操作和决策。
今天你可能已经遇到了其中之一,无论是在你最喜爱的在线商店上选择类别,还是在注册表单上选择你的出生日期。
但如果我告诉你,有一个秘密配方可以将你的下拉菜单从平凡提升到华丽呢?
加入我,我将解剖复合组件模式的奥秘,并利用其能力构建一个动态下拉组件。
(本文视频讲解:java567.com)
下拉菜单组件在用户界面设计中起着至关重要的作用,作为交互式菜单,它们赋予用户从一系列选项中进行选择的能力。通常,它们由一个可点击的区域组成,在激活时展示用户可以进行选择的一系列选项。
下拉菜单组件的操作很简单:当用户与之交互时——通常通过点击或轻触——下拉菜单会展开,显示可用的选项。
随后,用户可以从这些选择中选择一个,然后该选择要么显示在下拉菜单本身中,要么用于更新界面中相关字段或元素。
下拉菜单组件提供了一种清晰高效的方法,向用户呈现各种选择,使其非常适用于需要同时保持整洁界面的情况下访问多个选项的场景。
下拉菜单还具有以下作用:
下拉菜单组件的示例可在此处看到:
展示下拉菜单演示
或在Semantic UI页面上。
复合组件模式就像使用乐高积木一样:你组装小的部件来创建更大更复杂的东西。在React中,这是一种巧妙的设计组件的方式,由几个小部件组成,它们能够无缝地协同工作。
想象一下你正在构建一个下拉菜单。与其创建一个处理所有事情的单一组件,不如将其拆分成较小、可重用的部件。你可能有一个用于下拉按钮的组件,另一个用于选项列表,还有一个用于处理状态和交互逻辑的组件。
复合组件示意图
这里有一个有趣的地方:这些小组件通过共享上下文进行通信。上下文就像是一个信使,可以在不需要通过组件树的每个级别传递信息的情况下,将信息从一个组件传递到另一个组件。
这是一个强大的工具,可以简化组件之间共享数据的过程,特别是当它们被深度嵌套时。
那么,为什么这种模式如此有益呢?
因此,虽然使用上下文来构建UI组件的想法起初可能看起来不同寻常,但这是一种巧妙的方式,可以创建动态和可重用的组件,从而赋予开发人员构建出色用户体验的能力。
在接下来的部分中,我们将深入探讨如何使用上下文将复合组件带入实际。
我已经准备了一个GitHub存储库,其中包含启动文件,以加快进度。只需克隆这个存储库并安装依赖项。
在这一部分,我们将使用常规的函数式React构建一个下拉菜单组件,然后将其与CC模式进行比较,以充分理解它们之间的区别。PS:你一定会喜欢复合组件模式的。😁
Oh Fo sho Snoop Dogg gif
我们将从创建下拉菜单组件的基本结构开始。这将涉及设置主下拉菜单容器、触发下拉菜单的按钮以及选项列表。
const Dropdown = () => {
return (
);
};
这将呈现:
下拉按钮呈现
然后将用户数组传递给下拉菜单,以创建用户列表。
const Dropdown = ({ usersArray }) => {
return (
);
};
这将呈现:
下拉列表呈现
目前,你的下拉列表默认显示。为了添加切换行为,请为其可见性创建一个状态。
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
然后将它们作为props传递给Dropdown
组件。
在看到结果之前,将一个切换函数附加到下拉按钮,用于将下拉状态更改为true。
const toggleDropdown = () => {
setIsDropdownOpen(true);
};
现在你的下拉组件应该是这样的:
const Dropdown = ({ usersArray, setIsDropdownOpen, isDropdownOpen }) => {
const toggleDropdown = () => {
setIsDropdownOpen(true);
};
return (
);
};
你的下拉现在的行为是这样的:
有条件地呈现下拉列表的下拉菜单
我知道你已经注意到你的下拉只能打开,而不能关闭。别担心,我们稍后会以更加简洁的方式来修复它。😉
相信过程
接下来,让我们创建一种方法来为任务分配用户。首先,在App
组件中创建一个状态以存储已分配的用户。
const [assignedList, setAssignedList] = useState([]);
然后将其作为props传递给Dropdown
组件。
要为任务分配用户,创建一个处理函数,首先检查你试图添加的用户是否已经在数组中,如果还没有,将其添加进去,如果已经存在,则将其移除。
function handleAssign(user) {
setAssignedList((prevList) => {
// 检查用户是否已存在于列表中
if (prevList.includes(user)) {
// 如果用户存在,则从列表中移除它
const updatedList = prevList.filter((item) => item !== user);
return updatedList;
} else {
// 如果用户不存在,则将其添加到列表中
return [...prevList, user];
}
});
}
为了确认这个函数是否有效,使用assignedList
数组为每个已分配的用户添加一个检查图标。
{usersArray.map((user) => (
- handleAssign(user)}
>
{assignedList.includes(user) &&
}
{user.name}
))}
通过这个改变,下拉应该会在点击每个用户时进行分配和取消分配。
为任务分配和取消分配用户
为了改进UI,让我们创建一个组件来显示所有已分配的用户。
创建一个AssignedList
组件,并传递其相应的状态。
然后使用已分配的数组来创建一些JSX。
function AssignedList({ assignedList, setAssignedList }) {
return (
已分配列表:
{assignedList?.map((user, index) => (
{index + 1}.
{user.name}
))}
);
}
现在测试你的组件应该得到:
使用AssignedList组件显示已分配的用户
最后一个改变是一个主观的选择,因为我更喜欢在没有当前有用户分配任务时显示其他内容。
{assignedList.length === 0 ? (
尚未为任务分配用户。
) : (
)}
这将带来UI:
在没有用户被分配时显示默认文本
现在,让我们开始主要内容。首先创建一个包装整个组件的上下文。
const UserAssignContext = createContext();
然后收集我们的下拉菜单及其组件所需的所有必要数据和函数。这包括已分配用户列表、更新该列表的函数以及下拉菜单当前是否打开等内容。
然后,将这些值提供给所有子组件。
const UserAssignDropdown = ({
children,
assignedList,
setAssignedList,
users,
}) => {
const UserAssignDropdownRef = useRef(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
return (
{
assignedList,
users,
UserAssignDropdownRef,
isDropdownOpen,
setIsDropdownOpen,
setAssignedList,
}}>
{children}
);
};
有了上下文设置好了,现在是时候制作组成我们下拉菜单的各个组件了。每个组件将与上下文交互,以访问和操作必要的数据和函数。
首先,从我们刚刚构建的组件中复制每个样式。
这个组件保持不变。
const Header = () => {
return ;
};
该组件从上下文中获取用于切换下拉菜单的函数。
const Close = () => {
const { setIsDropdownOpen } = useContext(UserAssignContext);
return (
{
e.stopPropagation();
setIsDropdownOpen(false);
}}>
关闭
);
};
该组件显示已分配的用户列表,并从列表中删除用户。
const AssignedList = () => {
const { assignedList, setAssignedList } = useContext(UserAssignContext);
function handleRemove(id) {
setAssignedList((assignedList) =>
assignedList.filter((user) => user.id !== id)
);
}
if (assignedList.length === 0)
return (
尚未为任务分配用户。
);
return (
已分配列表:
{assignedList?.map((user, index) => (
handleRemove(user.id)}>
{index + 1}.
{user.name}
))}
);
};
该组件表示每个用户,并具有添加和从已分配列表中删除用户的功能。
const Item = ({ user }) => {
const { assignedList, setAssignedList } = useContext(UserAssignContext);
function handleAssign(user) {
setAssignedList((prevList) => {
// 检查用户是否已存在于列表中
if (prevList.includes(user)) {
// 如果用户存在,则从列表中移除它
const updatedList = prevList.filter((item) => item !== user);
return updatedList;
} else {
// 如果用户不存在,则将其添加到列表中
return [...prevList, user];
}
});
}
return (
- handleAssign(user)}>
{assignedList.includes(user) &&
}
{user.name}
);
};
该组件控制显示List
组件(浮动下拉列表)。
const Button = () => {
const { setIsDropdownOpen } = useContext(UserAssignContext);
return (
);
};
要将这些组件组合成一个单一的复合组件,你需要将每个组件分配给父组件,如下所示:
UserAssignDropdown.List = ListContainer;
UserAssignDropdown.Item = Item;
UserAssignDropdown.Header = Header;
UserAssignDropdown.Button = Button;
UserAssignDropdown.AssignedList = AssignedList;
UserAssignDropdown.Close = Close;
接下来,在你的App
组件中作为包装组件导入你的复合组件,并传入适当的状态。
export default function App() {
const [assignedList, setAssignedList] = useState([]);
return (
复合组件模式
);
}
然后在包装器中呈现适当的子组件。
export default function App() {
const [assignedList, setAssignedList] = useState([]);
return (
复合组件模式
);
}
最后,使用之前创建的自定义钩子在单击组件外部时关闭下拉菜单。
const UserAssignContext = createContext();
const UserAssignDropdown = ({
children,
assignedList,
setAssignedList,
users,
}) => {
const UserAssignDropdownRef = useRef(null);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
useClickOutside(UserAssignDropdownRef, () => {
setIsDropdownOpen(false);
});
return (
{
assignedList,
users,
UserAssignDropdownRef,
isDropdownOpen,
setIsDropdownOpen,
setAssignedList,
}}>
{children}
);
};
至此,你的组件就完成了相同的功能!
复制相同功能的复合组件模式
但是为什么要停在这里呢?
使用这种模式,更改组件的外观就像更改它们在父组件中呈现顺序一样简单。例如,如果你想要首先显示按钮,你只需在父组件中更改顺序。
UI会相应地作出反应。
复合组件中呈现顺序已更改
此组件还灵活到足以通过 props 更改元素的布局。
只需通过父级传递样式 props:
并在子级接收这些 props:
const Button = ({ listStyles }) => {
const { setIsDropdownOpen, UserAssignDropdownRef } =
useContext(UserAssignContext);
return (
);
};
const ListContainer = ({ listStyles }) => {
const { users, isDropdownOpen } = useContext(UserAssignContext);
return (
isDropdownOpen && (
{users?.map((user, index) => (
))}
)
);
};
你可以轻松更改组件的外观。
使用 props 自定义复合组件
好的,让我们退后一步,比较一下我们刚刚探讨过的两种方法。
这里是你可能需要的所有资源链接。
最后,这两种方法都在你的代码中有其用武之地。常规方法就像你可靠的旧搅拌碗 - 可靠而熟悉,但也许并不适合每种食谱。
复合组件模式就像一个组织良好的厨房,一切井然有序,准备就绪。它可能需要一些设置,但从长远来看,它可以让你的生活变得更轻松。所以,根据你要做的事情,选择适合你口味的方法 - 并愉快地编码吧! 🍰🎨
(本文视频讲解:java567.com)