首页 > 解决方案 > 使用 useState() 为 Nav 中的活动链接共享组件状态

问题描述

我正在尝试学习对框架的反应和相当新的知识。我正在尝试使用具有响应性的 material-ui 创建一个简单的导航栏组件(将显示中型设备及以上设备上的所有链接,并在小型设备上打开一个侧抽屉)。我根据自己的喜好进行了大部分设置,但是,我目前遇到的问题是根据我所在的页面获取和设置活动链接。

它似乎在中型设备及更高版本上正常工作,但是当转换到较小的设备时,链接不会正确更新,因为它会保留中型屏幕集的活动链接,同时更新侧抽屉活动链接。

导航栏.js

const Navbar = () => {
    const classes = useStyles();
    const pathname = window.location.pathname;
    const path = pathname === '' ? '' : pathname.substr(1);
    const [selectedItem, setSelectedItem] = useState(path);

    const handleItemClick = (event, selected) => {
        setSelectedItem(selected);
        console.log(selectedItem);
    };

    return (
        <>
            <HideNavOnScroll>
                <AppBar position="fixed">
                    <Toolbar component="nav" className={classes.navbar}>
                        <Container maxWidth="lg" className={classes.navbarDisplayFlex}>
                            <List>
                                <ListItem
                                    button
                                    component={RouterLink}
                                    to="/"
                                    selected={selectedItem === ''}
                                    onClick={event => handleItemClick(event, '')}
                                >
                                    <ListItemText className={classes.item} primary="Home" />
                                </ListItem>
                            </List>

                            <Hidden smDown>
                                <List
                                    component="nav"
                                    aria-labelledby="main navigation"
                                    className={classes.navListDisplayFlex}
                                >
                                    <ListItem
                                        button
                                        component={RouterLink}
                                        to="/account/login"
                                        selected={selectedItem === 'account/login'}
                                        onClick={event => handleItemClick(event, 'account/login')}
                                    >
                                        <ListItemText className={classes.item} primary="Login" />
                                    </ListItem>

                                    <ListItem
                                        button
                                        component={RouterLink}
                                        to="/account/register"
                                        selected={selectedItem === 'account/register'}
                                        onClick={event => handleItemClick(event, 'account/register')}
                                    >
                                        <ListItemText className={classes.item} primary="Register" />
                                    </ListItem>

                                </List>
                            </Hidden>

                            <Hidden mdUp>
                                <SideDrawer />
                            </Hidden>
                        </Container>
                    </Toolbar>
                </AppBar>
            </HideNavOnScroll>

            <Toolbar id="scroll-to-top-anchor" />

            <ScrollToTop>
                <Fab aria-label="Scroll back to top">
                    <NavigationIcon />
                </Fab>
            </ScrollToTop>
        </>
    )
}

SideDrawer.js

const SideDrawer = () => {
  const classes = useStyles();
  const [state, setState] = useState({ right: false });

  const pathname = window.location.pathname;
  const path = pathname === "" ? "" : pathname.substr(1);
  const [selectedItem, setSelectedItem] = useState(path);

  const handleItemClick = (event, selected) => {
    setSelectedItem(selected);
    console.log(selectedItem);
  };

  const toggleDrawer = (anchor, open) => (event) => {
    if (
      event &&
      event.type === "keydown" &&
      (event.key === "Tab" || event.key === "Shift")
    ) {
      return;
    }

    setState({ ...state, [anchor]: open });
  };

  const drawerList = (anchor) => (
    <div
      className={classes.list}
      role="presentation"
      onClick={toggleDrawer(anchor, false)}
      onKeyDown={toggleDrawer(anchor, false)}
    >
      <List component="nav">
        <ListItem
          button
          component={RouterLink}
          to="/account/login"
          selected={selectedItem === "account/login"}
          onClick={(event) => handleItemClick(event, "account/login")}
        >
          <ListItemText className={classes.item} primary="Login" />
        </ListItem>

        <ListItem
          button
          component={RouterLink}
          to="/account/login"
          selected={selectedItem === "account/register"}
          onClick={(event) => handleItemClick(event, "account/register")}
        >
          <ListItemText className={classes.item} primary="Register" />
        </ListItem>
      </List>
    </div>
  );

  return (
    <React.Fragment>
      <IconButton
        edge="start"
        aria-label="Menu"
        onClick={toggleDrawer("right", true)}
      >
        <Menu fontSize="large" style={{ color: "white" }} />
      </IconButton>

      <Drawer
        anchor="right"
        open={state.right}
        onClose={toggleDrawer("right", false)}
      >
        {drawerList("right")}
      </Drawer>
    </React.Fragment>
  );
};

代码沙箱- https://codesandbox.io/s/async-water-yx90j

我在 SO 上遇到了这个问题:是否可以使用 React 中的 useState() 钩子在组件之间共享状态?,这表明我需要将状态提升到一个共同的祖先组件,但我不太明白如何在我的情况下应用它。

标签: javascriptreactjsreact-hooksmaterial-ui

解决方案


我建议暂时搁置您的代码并为这种提升状态理解做一个操场。提升状态是在不相关的组件之间共享状态的基本策略。基本上在某个共同的祖先是 state 和 setState 将存在的地方。在那里你可以作为道具传递给它的孩子:

const Parent = () => {

  const [name, setName] = useState('joe')

  return (
    <>
      <h1>Parent Component</h1>
      <p>Child Name is {name}</p>
      <FirstChild name={name} setName={setName} />
      <SecondChild name={name} setName={setName} />
    </>
  )
}

const FirstChild = ({name, setName}) => {
  return (
    <>
      <h2>First Child Component</h2>
      <p>Are you sure child is {name}?</p>
      <button onClick={() => setName('Mary')}>My Name is Mary</button>
    </>
  )
}

const SecondChild = ({name, setName}) => {
  return (
    <>
      <h2>Second Child Component</h2>
      <p>Are you sure child is {name}?</p>
      <button onClick={() => setName('Joe')}>My Name is Joe</button>
    </>
  )
}

如您所见,只有一种状态,一种真理来源。状态位于Parent并向下传递到它的children. 现在,如果您需要将您的州定位在某个遥远的 GreatGrandParent,有时会很麻烦。你必须把每个孩子都传下去,直到到达那里,这很烦人。如果你发现自己处于这种情况,你可以使用React Context API。而且,对于最复杂的状态管理,还有redux 之类的解决方案。


推荐阅读