为什么 setState(null) 不改变状态值

时间:2021-05-10 13:43:12

标签: javascript reactjs react-hooks use-state

我正在尝试使用 React 的 setState 钩子打开和关闭我的 Material UI <Menu />。我以 this sandbox code 为例。然而,我的实现似乎并没有关闭菜单。

打开它工作正常(使用 setAnchorEl(event.currentTarget)),但使用 setAnchorEl(null) 关闭它不知道。函数 handleClose() 运行(console.log() 打印正常),但 anchorEl 的值没有改变。我使用 useEffect() 钩子检查了这一点,它在初始化时打印 null,打开时打印 <li> 元素,尝试关闭时不打印。

我已经尝试使用 let 而不是 const 作为 (set)anchorElisProfileMenuOpen 定义,但这不起作用。我还检查了 this post,但就我所见,我的 <MenuAppBar /> 组件并未卸载(它无条件地在根部呈现)。

我一定是忽略了一些细节。有人能在下面看到吗?

const {useState, useEffect} = React;
const { ClickAwayListener, Menu, MenuItem, IconButton } = MaterialUI;

function MenuAppBar(props) {
  const [anchorEl, setAnchorEl] = useState(null);
  const isProfileMenuOpen = Boolean(anchorEl);

  // For debugging purposes:
  useEffect(() => {
    console.log(anchorEl);
  }, [anchorEl]);

  const handleClose = () => {
    console.log("Closing menu"); // This prints, so function runs
    setAnchorEl(null); // This doesn't seem to set anchorEl to null
  };

  const handleProfileMenuOpen = (event) => {
    setAnchorEl(event.currentTarget); // This does work
  }

  return (
    <ClickAwayListener onClickAway={handleClose}>
      <MenuItem onClick={handleProfileMenuOpen}>
        <IconButton
          style={{ backgroundColor: "#F5B089" }}
        />
        <Menu
          anchorEl={anchorEl}
          keepMounted
          open={isProfileMenuOpen}
          onClose={handleClose}
        >
          <MenuItem onClick={handleClose}>Profile</MenuItem>
          <MenuItem onClick={handleClose}>My account</MenuItem>
          <MenuItem>Logout</MenuItem>
        </Menu>
      </MenuItem>
    </ClickAwayListener>
  );
}

ReactDOM.render(
  <MenuAppBar />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script>
<div id="react"></div>

1 个答案:

答案 0 :(得分:4)

发生的事情是在调用 handleClose 之后,再次调用了 handleProfileMenuOpen。所以你最终在同一个点击事件中两次调用 setAnchorEl,所以 React 只重新渲染一次组件 - 将它设置为 handleProfileMenuOpen与 Stack Snippet 中相同的 li)。

如果您阻止事件传播,它会阻止 handleProfileMenuOpen 调用:

const handleClose = (event) => {
    console.log("Closing menu");
    setAnchorEl(null);
    event.stopPropagation(); // <=====
};

现场示例:

const {useState, useEffect} = React;
const { ClickAwayListener, Menu, MenuItem, IconButton } = MaterialUI;

function MenuAppBar(props) {
  const [anchorEl, setAnchorEl] = useState(null);
  const isProfileMenuOpen = Boolean(anchorEl);
  
  console.log(anchorEl);

  const handleClose = (event) => {
    console.log("Closing menu"); // This prints, so function runs
    setAnchorEl(null); // This doesn't seem to set anchorEl to null
    event.stopPropagation();
  };

  const handleProfileMenuOpen = (event) => {
    console.log("Opening menu");
    setAnchorEl(event.currentTarget); // This does work
  }

  return (
    <ClickAwayListener onClickAway={handleClose}>
      <MenuItem onClick={handleProfileMenuOpen}>
        <IconButton
          style={{ backgroundColor: "#F5B089" }}
        />
        <Menu
          anchorEl={anchorEl}
          keepMounted
          open={isProfileMenuOpen}
          onClose={handleClose}
        >
          <MenuItem onClick={handleClose}>Profile</MenuItem>
          <MenuItem onClick={handleClose}>My account</MenuItem>
          <MenuItem>Logout</MenuItem>
        </Menu>
      </MenuItem>
    </ClickAwayListener>
  );
}

ReactDOM.render(
  <MenuAppBar />,
  document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@material-ui/core@latest/umd/material-ui.development.js"></script>
<div id="react"></div>


旁注:如果您想在组件重新渲染时看到 anchorEl 发生变化,则不需要 useEffect 调用,只需将其记录在组件函数的顶层(或放置一个const [anchorEl ... 行上的断点)。请记住,每次呈现组件时都会调用组件函数,以便始终向您显示 anchorEl 的最新版本。