React styled components中父组件.map内如何访问子组件state
问题原因
visible是Navbar.ListItem组件内部通过useState声明的局部状态,作用域仅在NavbarListItem函数组件内部,外层Navbar组件的map遍历逻辑不在该作用域下,直接访问会触发变量未定义错误。- 你已经在
ListItem中通过Ctx.Provider向下传递状态,但没有在Provider覆盖的子组件中通过useContext消费对应值,传递的状态没有被实际使用。
解决方案
根据你的组件拆分逻辑,二选一即可:
方案1:保留子组件自治状态,通过Context消费值
不需要改动你原来的状态定义位置,把map中渲染的单个菜单项内容抽为独立组件,在组件内消费Context拿到visible值即可,符合单向数据流原则:
import { createContext, useContext, useState } from 'react'; // 提前定义Context并导出,供子组件消费 const Ctx = createContext<{ visible: boolean; setVisible: (v: boolean | ((prev: boolean) => boolean)) => void; }>({ visible: false, setVisible: () => {}, }); // 抽离单个菜单项组件,该组件渲染在Ctx.Provider的子树内,可以直接消费Context function NavMenuItem({ item }: { item: (typeof NavData)[number] }) { const { visible } = useContext(Ctx); return ( <Navbar.Link> <Navbar.Image src={visible ? item.openIcon : item.closedIcon} /> <p>{item.name}</p> </Navbar.Link> ); } export default function Navbar({ children, ...restProps }: IProps) { return ( <Container {...restProps}> <Inner> <Navbar.List> {NavData.map((item, i) => ( <Navbar.ListItem key={i}> <NavMenuItem item={item} /> </Navbar.ListItem> ))} </Navbar.List> </Inner> </Container> ); } Navbar.List = function NavbarList({ children, ...restProps }: IProps) { return <List {...restProps}>{children}</List>; }; Navbar.ListItem = function NavbarListItem({ children, ...restProps }: IProps) { const [visible, setVisible] = useState(false); // 原有Provider逻辑保留 return ( <Ctx.Provider value={{ visible, setVisible }}> <ListItem onClick={() => setVisible((curr) => !curr)} {...restProps}> {children} </ListItem> </Ctx.Provider> ); }; Navbar.Link = function NavbarLink({ children, ...restProps }: IProps) { return <Link {...restProps}>{children}</Link>; }; Navbar.Image = function NavbarImage({ children, ...restProps }: IProps) { return <Image {...restProps} />; };
方案2:状态上移到父组件统一管理
如果不想引入Context,可以把visible状态从ListItem上移到Navbar组件统一维护,父组件直接掌握所有菜单项的展开状态,不需要跨组件访问子组件内部值:
import { useState } from 'react'; export default function Navbar({ children, ...restProps }: IProps) { // 用数组维护每个菜单项的展开状态,索引和NavData一一对应 const [visibleList, setVisibleList] = useState<boolean[]>(() => NavData.map(() => false)); const toggleVisible = (index: number) => { setVisibleList(prev => prev.map((val, i) => i === index ? !val : val)); }; return ( <Container {...restProps}> <Inner> <Navbar.List> {NavData.map((item, i) => ( <Navbar.ListItem key={i} onClick={() => toggleVisible(i)}> <Navbar.Link> {/* 直接从父组件维护的状态中取值 */} <Navbar.Image src={visibleList[i] ? item.openIcon : item.closedIcon} /> <p>{item.name}</p> </Navbar.Link> </Navbar.ListItem> ))} </Navbar.List> </Inner> </Container> ); } Navbar.List = function NavbarList({ children, ...restProps }: IProps) { return <List {...restProps}>{children}</List>; }; // ListItem不再维护内部状态,直接透传props即可 Navbar.ListItem = function NavbarListItem({ children, ...restProps }: IProps) { return <ListItem {...restProps}>{children}</ListItem>; }; Navbar.Link = function NavbarLink({ children, ...restProps }: IProps) { return <Link {...restProps}>{children}</Link>; }; Navbar.Image = function NavbarImage({ children, ...restProps }: IProps) { return <Image {...restProps} />; };
如果你需要实现手风琴效果(同一时间仅展开一个菜单项),方案2里不需要用数组存状态,直接用一个number类型的state存当前激活项的索引即可,逻辑更简洁。
内容的提问来源于stack exchange,提问作者JammyDodger
相关产品推荐
相关产品推荐

