我目前正在利用React和Tailwind构建一个投资组合网站。我正在尝试实现一个移动菜单按钮和下拉菜单,使用CSS转换进行动画化。我有条件地申请";打开";根据父元素中设置的状态,使用类名库将类添加到组件。不幸的是,每当父元素中的状态发生变化时,即单击按钮并更新状态时,CSS就会立即更新,而不是像预期的那样转换。转换将立即应用,而不是随时间应用。
我可以启用/禁用CSS规则,以强制在检查器中进行动画转换。如果我从父元素中移除状态挂钩,并在每个子组件中使用单独的状态,则转换也将单独正确地播放,但我无法弄清楚如何使两个转换同时播放。
我已经能够追踪到它,我认为这是因为父组件正在再次渲染并停止其轨迹中的转换,但我不知道如何避免由此产生的状态更改带来的更新。我已经尝试过对父组件进行记忆,但它仍然无法正确渲染。
我们将非常感谢您对如何正确进行过渡的任何帮助。
父元素:
...
const [mobileNavOpen, setMobileNavOpen] = useState(false);
const handleMobileMenuClick = () => {
setMobileNavOpen(!mobileNavOpen);
};
...
return (
<nav>
<CenteredContainer>
{/* Padding for absolute elements don't inherit. If padding needs to be changed on center container, also change it here */}
<div className="absolute top-0 left-0 right-0 z-10 flex items-center justify-between px-6 pt-4 mx-auto md:pt-2 md:px-0">
<Logo />
<NavLinks />
<MobileMenuButton open={mobileNavOpen} />
</div>
</CenteredContainer>
<MobileNavMenu open={mobileNavOpen} />
</nav>
);
子元素
const MobileMenuButton = ({ open }) => {
const tailwindClasses = "flex md:hidden mt-2 ";
var animateMobileMenu = classNames(`${tailwindClasses} nav-icon`, {
open: open,
});
return (
<div onClick={handleMobileMenuClick} className={animateMobileMenu}>
<span></span>
<span></span>
<span></span>
</div>
);
};
const MobileNavMenu = ({ open }) => {
const tailwindClasses =
"h-1/2 w-5/6 bg-white right-0 left-0 top-20 mx-auto flex flex-col z-20 ";
const displayMobileNavLinks = classNames(
`${tailwindClasses} mobile-nav-menu`,
{
open: open,
}
);
return (
<div className={displayMobileNavLinks}>
<ul>
<li>Test</li>
<li>Test</li>
<li>Test</li>
<li>Test</li>
<li>Test</li>
</ul>
</div>
);
};
CSS:
.nav-icon {
width: 40px;
height: 40px;
position: relative;
cursor: pointer;
}
.nav-icon span {
position: absolute;
height: 15%;
width: 100%;
background-color: #6ee7b7;
border-radius: 9px;
opacity: 1;
left: 0;
transform: rotate(0deg);
transition: all 0.5s ease;
}
.nav-icon span:nth-child(1) {
top: 0%;
transform-origin: left center;
}
.nav-icon span:nth-child(2) {
top: 35%;
transform-origin: left center;
}
.nav-icon span:nth-child(3) {
top: 70%;
transform-origin: left center;
}
.nav-icon.open span:nth-child(1) {
transform: rotate(45deg);
left: 8px;
}
.nav-icon.open span:nth-child(2) {
width: 0%;
opacity: 0;
}
.nav-icon.open span:nth-child(3) {
transform: rotate(-45deg);
left: 8px;
}
您可以尝试从以下内容更改父组件中的jsx吗:
<MobileMenuButton open={mobileNavOpen} />
到此:
{MobileMenuButton({ open: mobileNavOpen })}
像函数一样调用您的子组件
在大多数情况下,如果子组件触发父组件的状态更改(在您的情况下为mobileNavOpen
(,则子组件和任何同级组件都应该能够毫无问题地利用更新后的状态。
然而,您偶然发现了一个边缘情况,由该模式触发的多个渲染(更具体地说,这些渲染的顺序(会阻碍CSS转换。
如果不有条件地渲染组件,就无法指定哪个组件将首先渲染或最后渲染。
如前所述:
这是因为React协调算法遵循深度优先遍历来开始工作,并且组件的渲染只有在其所有子级的渲染完成后才完成(completeWork(。因此,树中的根组件将始终是最后一个完成渲染的组件。
这意味着您的父组件将是最后一个渲染的组件;不难想象这会切断孩子们的CSS转换。
您提到在父级上使用React.memo
,但这相当于shouldComponentUpdate
,只会阻止父级重新渲染,这将阻止新状态作为道具传递给子级;反过来又破坏了参与转换的状态更新的目的。
在孩子身上使用React.memo
同样违反直觉。如果子级不重新渲染,则CSS类不会更新;没有过渡。
在这种情况下,您需要重构组件,以确保状态更改(以及随后的重新提交(只发生在具有转换的子组件中或只需将所有这些简化为一个组件。
由于触发状态更新的MobileMenuButton
与MobileNavMenu
明显不同,我认为您更适合后者。
你可能不需要看代码,但我无论如何都会发布它。您可以考虑将其转换为类组件来清理一些
const [mobileNavOpen, setMobileNavOpen] = useState(false);
const tailwindClassesBtn = "flex md:hidden mt-2 ";
const animateMobileMenu = classNames(`${tailwindClassesBtn} nav-icon`,
{
open: mobileNavOpen,
});
const tailwindClasses =
"h-1/2 w-5/6 bg-white right-0 left-0 top-20 mx-auto flex flex-col z-20 ";
const displayMobileNavLinks = classNames(
`${tailwindClasses} mobile-nav-menu`,
{
open: mobileNavOpen,
}
);
const handleMobileMenuClick = () => {
setMobileNavOpen(!mobileNavOpen);
};
return (
<nav>
<CenteredContainer>
{/* Padding for absolute elements don't inherit. If padding needs to be changed on center container, also change it here */}
<div className="absolute top-0 left-0 right-0 z-10 flex items-center justify-between px-6 pt-4 mx-auto md:pt-2 md:px-0">
<Logo />
<NavLinks />
<div onClick={handleMobileMenuClick} className={animateMobileMenu}>
<span></span>
<span></span>
<span></span>
</div>
</div>
</CenteredContainer>
<div className={displayMobileNavLinks}>
<ul>
<li>Test</li>
<li>Test</li>
<li>Test</li>
<li>Test</li>
<li>Test</li>
</ul>
</div>
</nav>
);
你可以使用一些技巧,通过转换两个元素,结合反应过渡组,但IMO这会使事情过于复杂。