如何用React Hooks构建可复用的动画组件
原文: https : //www.freecodecamp.org/news/animating-visibility-with-css-an-example-of-react-hooks/
译文:前端技术小哥
Restration Hooks取悦了开发人员。但对我来说,我已经开始对Hooks 感到疲劳。
React Hooks的示例,而不仅仅是“新方法”。正如大家根据本文的标题所猜测的,这个示例是一个动画。但机缘巧合下我改变了我的看法。
我正在开发一个使用网格中的卡片的阵营应用程序。当一个项目被删除时,我想让它的退出动画化,就像这样。
(我的目标)
不巧的是,这其实和我预计的有些许细微差别。而我的解决方案让我很好地利用了React Hooks。
我们要做什么?
从一个基线示例应用程序开始
逐渐增加消失的动画元素,把难点挑出来重点处理
一旦我们实现了所需的动画,我们将重构一个可重用的动画组件
我们将使用此组件使侧边栏和导航栏具有动画效果
以及...(你需要读/跳到最后)
对于没有耐心的人,这里是这个项目的代码的GitHub repo。每个步骤都有标签。(有关每个标记的链接和描述,请参见自述)。
基线
我用创建反应的应用内创建了一个简单的应用程序。它有一个简单的卡片网格。我们可以隐藏单独的卡片。
(没有动画 - 卡片消失得不连贯)
这个的代码是很基础的,结果也是无趣的。当用户单击眼图标按钮时,会我们更改卡片的display属性。
function Box({ word }) { const color = colors[Math.floor(Math.random() * 9)]; const [visible, setVisible] = useState(true); function hideMe() { setVisible(false); } let style = { borderColor: color, backgroundColor: color }; if (!visible) style.display = "none"; return ( <div className="box" style={style}> {" "} <div className="center">{word}</div>{" "} <button className="button bottom-corner" onClick={hideMe}> {" "} <i className="center far fa-eye fa-lg" />{" "} </button>{" "} </div> ); }
(是的,在上面我使用了鱼钩,但这个用法并不有趣。)
添加动画
我没有建立自己的动画库,而是寻找类似animate.css的动画库.React动画-CSS是一个很棒的动画库,它提供了一个围绕animate.css的包装。
npm install --save react-animated-css
把animate.css添加到index.html的
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.css" />
上面在的Box组件中,我们将其渲染更改为
return ( <Animated animationIn="zoomIn" animationOut="zoomOut" isVisible={visible}> <div className="box" style={style}> <div className="center">{word}</div> <button className="button bottom-corner" onClick={hideMe}> <i className="center far fa-eye fa-lg" /> </button> </div> </Animated> );
和我们中预期的不相符
但是animate.css设置可以opacity状语从句:其他的CSS属性的动画;不能我们在display属性上进行CSS转换因此,一个不可见的对象仍然存在,它占用了文档流中的空间。
如果大家试着谷歌一下,就会发现有一些解决方案建议使用计时器在动画结束时设置display: none。
所以我们可以补充一点
function Box({ word }) { const color = colors[Math.floor(Math.random() * 9)]; const [visible, setVisible] = useState(true); const [fading, setFading] = useState(false); function hideMe() { setFading(true); setTimeout(() => setVisible(false), 650); } let style = { borderColor: color, backgroundColor: color }; return ( <Animated animationIn="zoomIn" animationOut="zoomOut" isVisible={!fading} style={visible ? null : { display: "none" }} > <div className="box" style={style}> <div className="center">{word}</div> <button className="button bottom-corner" onClick={hideMe}> <i className="center far fa-eye fa-lg" /> </button> </div> </Animated> ); }
(注意:默认动画持续时间为1000毫秒我使用650毫秒作为超时,以便在设置display。属性之前最小化停滞感这是一个优先考虑的问题)
这将给我们带来预期中的效果。
(奈思!)
创建可复用组件
我们可以在这里停下来,但有两个问题(对我来说):
1、我不想复制/粘贴Animated块,样式和函数来重新创建此效果 2、Box组件混合了不同类型的逻辑,即违反了关注点分离。具体来说,Box的基本功能是使用卡片呈现其内容。但动画细目混杂在了一起。
类组件
我们可以创建一个传统的阵营类组件来管理动画状态:切换可见性并设置displayCSS属性的超时。
class AnimatedVisibility extends Component { constructor(props) { super(props); this.state = { noDisplay: false, visible: this.props.visible }; } componentWillReceiveProps(nextProps, nextContext) { if (!nextProps.visible) { this.setState({ visible: false }); setTimeout(() => this.setState({ noDisplay: true }), 650); } } render() { return ( <Animated animationIn="zoomIn" animationOut="zoomOut" isVisible={this.state.visible} style={this.state.noDisplay ? { display: "none" } : null} > {this.props.children} </Animated> ); } }
然后用于
function Box({ word }) { const color = colors[Math.floor(Math.random() * 9)]; const [visible, setVisible] = useState(true); function hideMe() { setVisible(false); } let style = { borderColor: color, backgroundColor: color }; return ( <AnimatedVisibility visible={visible}> <div className="box" style={style}> <div className="center">{word}</div> <button className="button bottom-corner" onClick={hideMe}> <i className="center far fa-eye fa-lg" /> </button> </div> </AnimatedVisibility> ); }
这的确创建了一个可复用组件,但它有点过于复杂。我们本可以做得更好。
React Hooks和useEffect
React Hooks是React 16.8中的新功能。它们为React组件中的生命周期和状态管理提供了一种更简单的方法.Hook useEffect为我们使用componentWillReceiveProps提供了一个精妙的替代品。代码变得更简单,我们可以再次使用功能组件。
function AnimatedVisibility({ visible, children }) { const [noDisplay, setNoDisplay] = useState(!visible); useEffect(() => { if (!visible) setTimeout(() => setNoDisplay(true), 650); else setNoDisplay(false); }, [visible]); const style = noDisplay ? { display: "none" } : null; return ( <Animated animationIn="zoomIn" animationOut="zoomOut" isVisible={visible} style={style} > {children} </Animated> ); }
使用useEffect hook有一些微妙之处。它主要用于副作用:更改状态,调用异步函数等。在我们的示例中,它根据先前的值可见设置内部noDisplay布尔值。通过向依赖关系数组的useEffect中添加visible,我们的useEffect 挂钩只会在visible值发生更改时调用。
我认为useEffect是一个比类组件杂波更好的解决方案,你们觉得呢?复用组件:侧边栏和导航栏
大家都喜欢侧边栏和导航栏。那我们分别加一个吧。
function ToggleButton({ label, isOpen, onClick }) { const icon = isOpen ? ( <i className="fas fa-toggle-off fa-lg" /> ) : ( <i className="fas fa-toggle-on fa-lg" /> ); return ( <button className="toggle" onClick={onClick}> {label} {icon} </button> ); } function Navbar({ open }) { return ( <AnimatedVisibility visible={open} animationIn="slideInDown" animationOut="slideOutUp" animationInDuration={300} animationOutDuration={600} > <nav className="bar nav"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </nav> </AnimatedVisibility> ); } function Sidebar({ open }) { return ( <AnimatedVisibility visible={open} animationIn="slideInLeft" animationOut="slideOutLeft" animationInDuration={500} animationOutDuration={600} className="on-top" > <div className="sidebar"> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> </AnimatedVisibility> ); } function App() { const [navIsOpen, setNavOpen] = useState(false); const [sidebarIsOpen, setSidebarOpen] = useState(false); function toggleNav() { setNavOpen(!navIsOpen); } function toggleSidebar() { setSidebarOpen(!sidebarIsOpen); } return ( <Fragment> <main className="main"> <header className="bar header"> <ToggleButton label="Sidebar" isOpen={sidebarIsOpen} onClick={toggleSidebar} /> <ToggleButton label="Navbar" isOpen={navIsOpen} onClick={toggleNav} /> </header> <Navbar open={navIsOpen} /> <Boxes /> </main> <Sidebar open={sidebarIsOpen} /> </Fragment> ); }
(达成复用)
但我们还没有结束......
我们可以在这里停下来。但正如我之前关于分离关注的评论一样,我宁愿避免在Box,Sidebar和Navbar的渲染方法中混合AnimatedVisibility组件。(这也是少量的重复。)
我们可以创建一个HOC。(事实上,我曾写了一篇关于动画和HOC的文章,如何在React中构建动画微交互。)但是由于状态管理,HOC通常涉及类组件。但是有了React Hooks,我们可以编写HOC(函数式编程方法)。
function AnimatedVisibility({ visible, children, animationOutDuration, disappearOffset, ...rest }) // ... same as before } function makeAnimated( Component, animationIn, animationOut, animationInDuration, animationOutDuration, disappearOffset ) { return function({ open, className, ...props }) { return ( <AnimatedVisibility visible={open} animationIn={animationIn} animationOut={animationOut} animationInDuration={animationInDuration} animationOutDuration={animationOutDuration} disappearOffset={disappearOffset} className={className} > <Component {...props} /> </AnimatedVisibility> ); }; } export function makeAnimationSlideLeft(Component) { return makeAnimated(Component, "slideInLeft", "slideOutLeft", 400, 500, 200); } export function makeAnimationSlideUpDown(Component) { return makeAnimated(Component, "slideInDown", "slideOutUp", 400, 500, 200); } export default AnimatedVisibility
在然后App.js中使用这些基于函数的HOC
function Navbar() { return ( <nav className="bar nav"> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </nav> ); } function Sidebar() { return ( <div className="sidebar"> <ul> <li>Item 1</li> <li>Item 2</li> <li>Item 3</li> </ul> </div> ); } const AnimatedSidebar = makeAnimationSlideLeft(Sidebar); const AnimatedNavbar = makeAnimationSlideUpDown(Navbar); function App() { const [navIsOpen, setNavOpen] = useState(false); const [sidebarIsOpen, setSidebarOpen] = useState(false); function toggleNav() { setNavOpen(!navIsOpen); } function toggleSidebar() { setSidebarOpen(!sidebarIsOpen); } return ( <Fragment> <main className="main"> <header className="bar header"> <ToggleButton label="Sidebar" isOpen={sidebarIsOpen} onClick={toggleSidebar} /> <ToggleButton label="Navbar" isOpen={navIsOpen} onClick={toggleNav} /> </header> <AnimatedNavbar open={navIsOpen} /> <Boxes /> </main> <AnimatedSidebar open={sidebarIsOpen} className="on-top"/> </Fragment> ); }
比起冒着推广自己作品的风险,我更喜欢干净的结果代码。下面是最终结果的沙盒。(此处更改)
现在还需要做什么呢?
对于简单的动画,我上面提到的方法效果很好。对于更复杂的情况,我会使用像react-motion这样的库。但除了制作动画,React Hooks提供了创建可读和简单代码的机会。但是,我们的思维需要调整。像useEffect这样的钩并不是所有生命周期方法的直接替代品。我们需要需要大量的学习和实验。
我建议大家可以查看像useHooks.com这样的网站和像反应使用这样的库,这是一个用于各种各样的用例的钩集合。
希望本文能帮助到您!
看之后
点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓-_-)
关注公众号「新前端社区」,享受文章首发体验!
每周重点攻克一个前端技术难点。