根据浏览器宽度和/或用户点击反应切换响应侧边栏

React toggle responsive sidebar based on browser width &/or user clicks

在这里反应菜鸟。我正在创建一个包含三个部分的仪表板。边栏 |导航栏和导航栏下方的内容。 侧边栏行为应该是:最初打开;当浏览器宽度 < 498px 时自动隐藏;当浏览器宽度增加超过 498px 时自动打开; open/close 当点击导航栏切换时,无论浏览器 window 宽度如何(即移动设备或桌面设备)。 我知道如何在 jQuery 中执行此操作,但想学习 React 方式。搜索论坛和 Google,我创建了一个工作模型。但是,我觉得代码可以简化或至少可以针对问题进行审计。希望前辈指教,欢迎大家帮忙。担心依赖警告和调用 2 个处理程序。没有使用 addEventListener,因为我读到 Safari 可能有问题。 Notes,我避免使用 css 媒体查询来自动隐藏,因为我不知道如何覆盖 display:none 的 css 文件媒体查询使用点击组件时来自 React。使用反应 17.0.2; bootstrap5.1.3;

Dashboard.js

import { useEffect, useRef, useState } from 'react';
import "./Dashboard.css";
import Navbar from "./Navbar";
import Sidebar from "./Sidebar";

const Dashboard = () => {
const sidebarRef = useRef(null); // used to get sidebar width
const [usMobile, setMobile] = useState("");
const mq = window.matchmedia("(max-width: 498px)");
const [firsTime, setFirsTime] = useState(true);

useEffect(() => {
  //handle sidebar display clicks from Navbar
  // makes/sets initial sidebar state to open
  // returns true when window is < 498px
  // unmount cleanup handler
  toggleSidebar();
  mq.addListener(toggleSidebar);
  return () => mq.removeListener(toggleSidebar);
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // warning about dependency here

useEffect(() => {
  // handles sidebar display based on resize
  // returns treu when window is < 498
  mq.addListener(hideSideBar);
  return () => mq.removeListener(hideSideBar);
  // eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // warning about dependency here

const hideSideBar = () => {
  if(mq.matches) { 
    setMobile(true);
  } else {
    setMobile(false);
  }
}

// toggle sidebar based on user clicks
const toggleSidebar = ( clicked ) => {
  let sidebarWidth = sidebarRef.current.offsetWidth;
  if (firsTime) {
     setShowSidebar(true);
     setMobile(false);
     setFirstTime(false);
  } else {
     if ( clicked = "yes") {
        if (sidebarWidth == 0){
           setShowSidebar(true);
           setMobile(false);
        } else {
           setShowSidebar(false);
        }
        clicked = "";
     }
  }
}

return (
  <span ref={sidebarRef}>
    {!isMobile && showSidebar && <Sidebar onClick={toggleSidebar} />}
  </span>
  <div className="flex-fill content-wrapper">
    <Navbar showSideBar={showSidebar} onClick={toggleSidebar} />
  </div>
);
}

export default Dashboard;

Navbar.js

import {useState} from 'react';

const Navbar = (props) => {
  // eslint-disable-next-line no-unused-vars
  // set is unused but it will not work otherwise
  const [clicked, setClicked] = useState("yes"); 

  return (
    <div className="d-flex justify-content-between bg-white py-2 ps-3 pe-4">
      <div className="col-auto">
        <button 
            className="menu-icon-btn"
            onClick={() => props.onClick(clicked)} >
        </button>
      </div>
    </div>
)
}
export default Navbar;

Sidebar.js

import {useState} from 'react';

const Sidebar = (props) => {
  const [clicked, setClicked] = useState('yes');

  return (
    <div className="d-flex flex-column flex-shrink-0 text-white bg-dark sidebar">
      <span className="fs4">Sidebar Title</span>
      <ul className="nav nav-pills flex-column mb-auto">
        <li className="nav-item">
            Home
        </li>
      </ul>
      <hr>
      <span id="toggle-x"
            className="btn btn-outline-primary border-0 inline d-md-none mx-auto mb-2">
            aria-label="Toggle Sidebar Nav"
            onClick={() => props.onClick(clicked)} >
              CLOSE
       </span>
    </div>   
  )
}

export default Sidebar;

Dashboard.css 大多数 css 来自 bootstrap;这些只是应用自定义样式

.menu-icon-btn {
  background: none;
  border: none;
  padding: 0;
}

.toggle-icon {
  width: 32px;
  height: 32px;
  fill: var(--medium-gray);
  cursor: pointer;
}

.menu-icon {
  width: 20px;
  height: 20px;
  fill: var(--medium-gray);
  cursor: pointer;
}

.sidebar,
.content-wrapper {
  height: 100vh;
}

.sidebar-wrapper {
  width: 200px;
}
/* Omitted the following as I was unable to force it to change from within React. In jQuery(show/hide seem to override this just fine.)*/
/* @media screen and (min-width: 0px) and (max-width: 700px) {
  .sidebar-wrapper {
    display: none;
  }
} */

New Dashboard.js

const Dashboard = () => {
    const isDesktop = () => window.innerWidth > 598;
    const [sidebarStatus, setSidebarStatus] = useState("");

    useEffect(() => {
        window.addEventListener("resize", () => {
            setSidebarStatus(isDesktop());
        });
        return () => window.removeEventListener("resize", isDesktop);
    }, []);

    const toggleSidebar = (open) => {
        setSidebarStatus(open);
    };

    return (
    
        {sidebarStatus && (
          <Sidebar showSideBar={sidebarStatus} onClick={toggleSidebar} />
        )}

    )
}

New Navbar.js

const Navbar = (props) => {

return (
        <button
          id="toggle"
          className="menu-icon-btn py-2"
          data-menu-icon-btn
          onClick={() => props.onClick(!props.showSideBar)}
        > Toggle Sidebar
        </button>
)
}


您的代码已损坏..我对您的代码做了一点改动:

import React, { useEffect, useRef, useState } from "react";
import "./Dashboard.css";
import Navbar from "./Navbar";
import Sidebar from "./Sidebar";

const Dashboard = () => {
  // i don't know what is using for..
  const sidebarRef = useRef(null); // used to get sidebar width
  const [isMobile, setMobile] = useState(document.body.clientWidth <= 498);
  // use 'init' | 'open' | 'close', that you don't need remember if suer clicked
  const [sidebarStatus, setSidebarStatus] = useState("init");

  useEffect(() => {
    // add listener only once, or many listeners would be created every render
    const mq = window.matchMedia("(max-width: 498px)");
    mq.addListener((res) => {
      setMobile(res.matches);
    });
    return () => mq.removeListener(toggleSidebar);
  }, []);

  // react use status change fire effects, fire function manually is tired
  // here calculate should show sidebar
  const showSidebar =
    sidebarStatus === "open" || (!isMobile && sidebarStatus === "init");

  const toggleSidebar = (open) => {
    setSidebarStatus(open ? "open" : "close");
  };


  return (
    <>
      <span ref={sidebarRef}>
        {showSidebar && <Sidebar onClick={toggleSidebar} />}
      </span>
      <div className="flex-fill content-wrapper">
        <Navbar showSideBar={showSidebar} onClick={toggleSidebar} />
      </div>
    </>
  );
};

export default Dashboard;

注意导航栏和侧边栏的 toggleSidebar 参数是常量 truefalse不要在不同的文件中使用状态