使用 React/TS 个功能组件一次切换一个列表项
Toggle one list item at a time with React/TS functional components
我有一个简单的组件,其中包含一些 mark-up:
的嵌套位
import React, { ReactElement } from "react";
type MenuItem = {
title: string;
subItems?: Array<string>;
};
type MenuConfig = Array<MenuItem>;
function Item({ item }: { item: MenuItem }): ReactElement {
const [showItem, setShowItem] = React.useState(false);
const handleShowItem = (): void => {
setShowItem(!showItem);
};
return (
<>
{item.subItems && (
<button onClick={() => handleShowItem()}>
{showItem ? "Hide" : "Expand"}
</button>
)}
{showItem && <SubItem item={item} />}
</>
);
}
function SubItem({ item }: { item: MenuItem }): ReactElement {
const { title } = item;
return (
<ul>
{item?.subItems?.map((subitem: string, i: number) => (
<li key={i}>
{subitem}
</li>
))}
</ul>
);
}
function Solution({ menuConfig }: { menuConfig: MenuConfig }): ReactElement {
return (
<>
{menuConfig.map((item: MenuItem, i: number) => (
<div key={i}>
<span>{item.title}</span>
<Item item={item} />
</div>
))}
</>
);
}
export default Solution;
这是我传递的内容:
menuConfig={[
{
title: "About",
},
{
title: "Prices",
subItems: ["Hosting", "Services"],
},
{
title: "Contact",
subItems: ["Email", "Mobile"],
},
]}
现在,它按预期运行,如果项目包含 subItems
,则会显示一个 Expand
按钮,如果单击该按钮,只会展开相关列表。
根据我的实施,我应该如何确保一次只打开一个列表?
因此,如果用户单击 Expand
按钮,其他先前展开的列表应该关闭。
我不能弄乱传入的数据,因此无法将 ID 添加到 object,但标题是唯一的。
我已经搜索过了,虽然有一些例子,但它们对我没有帮助,我就是无法理解这个问题。
这是 React 上下文的完美用例。您本质上想要在菜单之间共享状态。您可以通过道具钻孔来实现这一点,但是上下文是一个更简洁的解决方案。
const MenuContext = createContext<{
expandedState?: [number, React.Dispatch<React.SetStateAction<number>>];
}>({});
function Solution(): ReactElement {
const expandedState = useState<number>(null);
const value = { expandedState };
return (
<>
<MenuContext.Provider value={value}>
{menuConfig.map((item: MenuItem, i: number) => (
<div key={i}>
<span>{item.title}</span>
<Item i={i} item={item} />
</div>
))}
</MenuContext.Provider>
</>
);
}
function Item({ item, i }: { item: MenuItem; i: number }): ReactElement {
const [expanded, setExpanded] = useContext(MenuContext)?.expandedState ?? [];
const shown = expanded === i;
return (
<>
{item.subItems && (
<button onClick={() => setExpanded?.(i)}>
{shown ? "Hide" : "Expand"}
</button>
)}
{shown && <SubItem item={item} />}
</>
);
}
我有一个简单的组件,其中包含一些 mark-up:
的嵌套位 import React, { ReactElement } from "react";
type MenuItem = {
title: string;
subItems?: Array<string>;
};
type MenuConfig = Array<MenuItem>;
function Item({ item }: { item: MenuItem }): ReactElement {
const [showItem, setShowItem] = React.useState(false);
const handleShowItem = (): void => {
setShowItem(!showItem);
};
return (
<>
{item.subItems && (
<button onClick={() => handleShowItem()}>
{showItem ? "Hide" : "Expand"}
</button>
)}
{showItem && <SubItem item={item} />}
</>
);
}
function SubItem({ item }: { item: MenuItem }): ReactElement {
const { title } = item;
return (
<ul>
{item?.subItems?.map((subitem: string, i: number) => (
<li key={i}>
{subitem}
</li>
))}
</ul>
);
}
function Solution({ menuConfig }: { menuConfig: MenuConfig }): ReactElement {
return (
<>
{menuConfig.map((item: MenuItem, i: number) => (
<div key={i}>
<span>{item.title}</span>
<Item item={item} />
</div>
))}
</>
);
}
export default Solution;
这是我传递的内容:
menuConfig={[
{
title: "About",
},
{
title: "Prices",
subItems: ["Hosting", "Services"],
},
{
title: "Contact",
subItems: ["Email", "Mobile"],
},
]}
现在,它按预期运行,如果项目包含 subItems
,则会显示一个 Expand
按钮,如果单击该按钮,只会展开相关列表。
根据我的实施,我应该如何确保一次只打开一个列表?
因此,如果用户单击 Expand
按钮,其他先前展开的列表应该关闭。
我不能弄乱传入的数据,因此无法将 ID 添加到 object,但标题是唯一的。
我已经搜索过了,虽然有一些例子,但它们对我没有帮助,我就是无法理解这个问题。
这是 React 上下文的完美用例。您本质上想要在菜单之间共享状态。您可以通过道具钻孔来实现这一点,但是上下文是一个更简洁的解决方案。
const MenuContext = createContext<{
expandedState?: [number, React.Dispatch<React.SetStateAction<number>>];
}>({});
function Solution(): ReactElement {
const expandedState = useState<number>(null);
const value = { expandedState };
return (
<>
<MenuContext.Provider value={value}>
{menuConfig.map((item: MenuItem, i: number) => (
<div key={i}>
<span>{item.title}</span>
<Item i={i} item={item} />
</div>
))}
</MenuContext.Provider>
</>
);
}
function Item({ item, i }: { item: MenuItem; i: number }): ReactElement {
const [expanded, setExpanded] = useContext(MenuContext)?.expandedState ?? [];
const shown = expanded === i;
return (
<>
{item.subItems && (
<button onClick={() => setExpanded?.(i)}>
{shown ? "Hide" : "Expand"}
</button>
)}
{shown && <SubItem item={item} />}
</>
);
}