我有我的个人网站,是我多年前使用 HTML + ejs 模板和 scss 编写的。我现在正在使用 nextjs 和 React styled-components 重写网站。
我在重现我之前的代码中的导航栏行为时遇到问题,请寻求帮助。这主要是一个 React/CSS 问题。
我希望我的导航栏是这样的:
我在简化代码时遇到了另一个问题。我对使用钩子确保组件在渲染之前安装的理解是否正确?我注意到,如果删除此挂钩,导航栏将在不应用样式的情况下呈现。我不确定这是否是正确的方法或者是否有更好的方法。
好的,这是调试导航栏的最小示例:
npx create-next-app --ts .
# ✔ Would you like to use ESLint with this project? … No / Yes
# ✔ Would you like to use `src/` directory with this project? … No / Yes
# ✔ Would you like to use experimental `app/` directory with this project? … No / Yes
# ✔ What import alias would you like configured? … @components/*
# Creating a new Next.js app in /Users/work/Desktop/next-minimal-navbar.
npm install styled-components
简化了以下文件:
index.tsx
:
import Head from 'next/head'
export default function Home() {
return (
<>
<Head>
<title>Hello, world!</title>
</Head>
<main>
<h1>Hello, world!</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
</p>
</main>
</>
)
}
_document.tsx
:
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
这是我们导入
Navbar
组件的地方:
_app.tsx
:
import type { AppProps } from 'next/app'
import NavBar from './NavBar';
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<NavBar />
<Component {...pageProps} />
</>
)
}
最后是
NavBar
组件:
import Link from 'next/link';
import { useState, useEffect } from 'react';
import styled from 'styled-components';
const NavContainer = styled.nav`
background-color: red;
overflow: hidden;
margin: 20px auto;
width: 90%;
@media screen and (max-width: 1024px) {
width: 95%;
}
`;
const CommonNavItem = styled.div<{ rightmost?: boolean }>`
cursor: pointer;
float: ${(props) => (props.rightmost ? 'right' : 'left')};
display: block;
color: inherit;
text-align: center;
padding: 14px 13px;
text-decoration: none;
font-size: 17px;
`;
// allows for argument to determine if align left or right
// inherit from Link component allows for linking to other pages
const NavItem = styled(CommonNavItem).attrs({ as: Link }) <{ rightmost?: boolean }>``;
const NavImage = styled.img`
height: 16px;
cursor: pointer;
`;
const DropDownContainer = styled.div`
float: left;
overflow: hidden;
`;
// the same as NavItem but no link
const DropDownLabel = styled(CommonNavItem)<{ rightmost?: boolean }>``;
const DropDownContent = styled.div`
display: none;
position: absolute;
background-color: green;
min-width: 160px;
z-index: 1;
${DropDownContainer}:hover & {
display: block;
}
${NavItem} {
float: none;
padding: 12px 16px;
text-align: left;
}
`;
// since using conditionals in components we must ensure that the component is mounted before rendering
// either this or use dynamic from next/dynamic
function NavBar() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);
if (!hasMounted) {
return null;
}
return (
<NavContainer>
<NavItem href='/'>Blog</NavItem>
<NavItem href='/'>Link1</NavItem>
<NavItem href='/'>Link2</NavItem>
<NavItem href='/'>Link3</NavItem>
<NavItem href='/'>Link4</NavItem>
<DropDownContainer>
<DropDownLabel>Drop Down Label</DropDownLabel>
<DropDownContent>
<NavItem href='/some-link'>Drop Down Item 1</NavItem>
<NavItem href='/chemistry'>Drop Down Item 1</NavItem>
</DropDownContent>
</DropDownContainer>
<NavItem rightmost href='https://www.linkedin.com/in/dereck/' target='_blank' title='LinkedIn'>
<NavImage src='/next.svg' />
</NavItem>
</NavContainer>
)
}
export default NavBar;
这是我之前版本的一些截图;您可以在此处访问其实时版本;我希望导航栏的新 React 版本充当此实时版本:https://derecksnotes.com/
这里的导航栏看起来应该是全屏的。
当您将鼠标悬停在下拉菜单上时,它会覆盖标签。
当您缩小窗口时,我希望出现一个汉堡菜单并隐藏“Link1-4”链接和下拉菜单。这就是我对 React 缺乏经验的地方,我应该做这个 css 还是使用钩子和条件等;我对如何实现这个感到困惑。
此外,我在让任何汉堡菜单代码与我的代码中当前的组件层次结构一起工作时遇到了很多麻烦。我嵌套元素的方式对我来说很有意义,我见过其他示例,其中有更多的元素让这变得令人困惑。
想要的结果。下面是我当前的实时版本的屏幕截图。
这是我希望将鼠标悬停在下拉菜单上时的样子:
我回到这个并解决了它。这是我的代码:
import Link from 'next/link';
import { useState, useEffect } from 'react';
import styled from 'styled-components';
import { FaBars, FaFilter, FaUser } from 'react-icons/fa';
import { theme } from '@styles/theme';
import { useDispatch } from 'react-redux';
import { toggleTagsFilter } from '@store/tagsFilterVisibilitySlice';
import Auth from '../modals/auth/Auth';
const HamburgerIcon = styled.div`
display: none;
float: right;
cursor: pointer;
padding: 14px 13px;
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
display: block;
}
`;
const ResponsiveMenu = styled.div<{ open: boolean }>`
display: ${props => (props.open ? 'block' : 'none')};
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
display: ${props => (props.open ? 'block' : 'none')};
}
@media screen and (min-width: ${theme.container.widths.min_width_mobile}) {
display: block;
}
`;
const NavContainer = styled.nav`
background-color: ${theme.container.background.colour.primary()};
overflow: hidden;
margin: 20px auto;
width: 90%;
color: ${theme.theme_colours[5]()};
border: 1px solid #ccc;
border-radius: 5px;
box-shadow: 1px 1px 20px rgba(153, 153, 153, 0.5), 0 0 20px rgba(100, 100, 40, 0.2) inset;
&:hover {
box-shadow: 1px 1px 20px rgba(153, 153, 153, 0.5);
}
@media screen and (max-width: ${theme.container.widths.min_width_snap_up}) {
width: 95%;
}
`;
const CommonNavItem = styled.div`
cursor: pointer;
display: block;
color: inherit;
text-align: center;
padding: 14px 13px;
text-decoration: none;
font-size: 17px;
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
width: 100%;
float: none;
text-align: left;
}
`;
// allows for argument to determine if align left or right
// inherit from Link component allows for linking to other pages
const NavLeftItem = styled(CommonNavItem).attrs({ as: Link }) <{ rightmost?: boolean }>`
float: left;
&:hover {
color: ${theme.text.colour.white()};
background-color: ${theme.theme_colours[5]()};
}
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
float: none;
}
`;
const NavRightItemLink = styled(CommonNavItem).attrs({ as: Link }) <{ rightmost?: boolean }>`
float: right;
`;
const NavRightItem = styled(CommonNavItem) <{ rightmost?: boolean }>`
float: right;
&:hover {
color: ${theme.text.colour.white()};
background-color: ${theme.theme_colours[5]()};
}
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
float: none;
}
`;
const DropDownContainer = styled.div`
float: left;
overflow: hidden;
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
width: 100%;
float: none;
text-align: left;
}
`;
// the same as NavItem but no link
const DropDownLabel = styled(CommonNavItem) <{ rightmost?: boolean }>`
&:hover {
color: ${theme.text.colour.white()};
background-color: ${theme.theme_colours[5]()};
}
`;
const DropDownContent = styled.div`
display: none;
position: absolute;
min-width: 160px;
z-index: 1;
border: 1px solid #ccc;
box-shadow: 1px 1px 10px #ccc;
background-color: ${theme.container.background.colour.primary()};
${DropDownContainer}:hover & {
display: block;
}
${NavLeftItem} {
float: none;
padding: 12px 16px;
text-align: left;
}
/* TODO: still not working as intended */
${NavLeftItem}:first-child {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
${NavLeftItem}:last-child {
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
border: none;
width: 100%;
position: relative;
float: none;
text-align: left;
box-shadow: none;
${NavLeftItem} {
padding-left: 30px;
}
}
`;
const DateTimeDisplay = styled.div`
cursor: pointer;
float: right;
display: block;
color: inherit;
text-align: center;
padding: 14px 13px;
text-decoration: none;
font-size: 17px;
@media screen and (max-width: ${theme.container.widths.min_width_mobile}) {
width: 100%;
position: relative;
float: none;
text-align: left;
}
`;
// since using conditionals in components we must ensure that the component is mounted before rendering
// either this or use dynamic from next/dynamic
function NavBar() {
const [hasMounted, setHasMounted] = useState(false);
const [dateTime, setDateTime] = useState<string | null>(null);
const [isAuthModalOpen, setIsAuthModalOpen] = useState(false);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const closeMenu = () => {
setIsMenuOpen(false);
};
// redux control of tag filter
// Error: `useDispatch` was conditionally called inside an `if` statement. Fixed: Moved `useDispatch` to the top-level, ensuring consistent hook order across renders.
const dispatch = useDispatch();
const handleToggleFilterClick = () => {
dispatch(toggleTagsFilter());
};
useEffect(() => {
setHasMounted(true);
const updateDateTime = () => {
const currentDate = new Date();
const displayDate = currentDate.toLocaleDateString('en-US', {
day: '2-digit',
month: 'short'
});
const displayTime = currentDate.toLocaleTimeString('en-US', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
setDateTime(`${displayDate} ${displayTime}`);
};
updateDateTime();
const interval = setInterval(updateDateTime, 1000);
return () => {
clearInterval(interval);
};
}, []);
if (!hasMounted) {
return null;
}
return (
<NavContainer>
<HamburgerIcon onClick={() => setIsMenuOpen(!isMenuOpen)}>
<FaBars />
</HamburgerIcon>
<NavLeftItem onClick={closeMenu} href='/'>Blog</NavLeftItem>
<ResponsiveMenu open={isMenuOpen}>
<NavLeftItem onClick={closeMenu} href='/courses'>Courses</NavLeftItem>
<NavLeftItem onClick={closeMenu} href='/references'>References</NavLeftItem>
<DropDownContainer>
<DropDownLabel>Dictionaries</DropDownLabel>
<DropDownContent>
<NavLeftItem onClick={closeMenu} href='/dictionaries/biology'>Biology Dictionary</NavLeftItem>
<NavLeftItem onClick={closeMenu} href='/dictionaries/chemistry'>Chemistry Dictionary</NavLeftItem>
</DropDownContent>
</DropDownContainer>
{/* <NavRightItemLink href='https://www.linkedin.com/in/dereck/' target='_blank' title='LinkedIn'>
<FaLinkedin />
</NavRightItemLink> */}
<DateTimeDisplay>{dateTime || "00 Jan 00:00:00"}</DateTimeDisplay>
<NavRightItem onClick={handleToggleFilterClick}>
<FaFilter />
</NavRightItem>
<NavRightItem onClick={() => setIsAuthModalOpen(true)}>
<FaUser />
</NavRightItem>
{isAuthModalOpen && <Auth onClose={() => setIsAuthModalOpen(false)} />}
</ResponsiveMenu>
</NavContainer>
)
}
export default NavBar;