使用 React Hooks 创建可复用的动画组件
Photo by Dylan Ferreira on Unsplash
原文: https://www.freecodecamp.org/news/animating-visibility-with-css-an-example-of-react-hooks/
作者: Christian Sepulveda
译者: Zou Li
提示: 文中的蓝色字体可点击“阅读原文”访问更多内容
动画总是会取悦用户。 看到各种文章的介绍,你可能会觉得开发者们喜欢使用 React Hooks,但我发现自己开始慢慢对 Hooks 产生厌倦了。
某个意外的发现让我对 React Hooks 有了新的认识,它不仅仅是一种新的开发方式。 也许你已经从文章标题猜到是什么了,没错,就是动画!
我正在开发一个基于 React 的,使用网格布局组合卡片组件的应用,当删除某个卡片组件时,为它添加动画效果,看起来像下面一样:
但是,和图中效果相比较始终还是有点细微差别。 在我的接下来的解决方案中,很好地利用了 React Hooks。
我们将要做什么?
-
开始构建一个基本的项目骨架
-
为元素的消失添加动画效果,解决一些小问题
-
最终效果实现后,将其重构为一个可复用的动画组件
-
在顶部导航和侧边导航中使用该动画组件
如果你没耐心,这里有整个项目的 仓库地址 ,每一步都有相应的标记(链接地址和描述参考 README 文件)。
骨架
我使用 create-react-app 创建了一个简单的应用程序,它是一个简单的卡片网格结构,每个单独卡片可以被隐藏。
实现代码很简单,效果也很无趣。 当用户点击眼睛图标时,我们改变卡片的 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 ({" "}); }{word}{" "} {" "}
上面的代码中使用到了 React Hooks,但这不是 Hooks 最有趣的用途。
添加动画
我没有构建自己的动画库,而是使用了一个像 animate.css 这样的动画库。 react-animated-css 是一个很好的库,它为 animate.css 提供了一个包装器。
安装 react-animated-css
npm install --save react-animated-css
在 index.html
中添加 animate.css
在上面的 Box
组件中,将渲染结果改为
return ();{word}
不 完全是我们想要的东西
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 (); }{word}
注意: 默认的动画时长是 1000ms,我使用的是 650ms,为了在设置 display
属性之前减少卡顿/暂停现象(这只是个人喜好)。
这样我们就能得到想要的效果。
构建一个可复用的组件
现在到此为止,但目前有两个问题(对于我来说)
-
我不想复制/粘贴
Animated
代码块,样式,功能,来重复实现相同效果。 -
Box
组件混合了不同类型的逻辑,例如: 违反了关注点分离的概念。 准确的说,Box
的主要功能是渲染卡片内容,但是动画细节混入了。
类组件
我们可以创建一个传统的 React 类组件来管理和动画相关的状态: 切换隐藏/显示,设置 display
属性的超时时间。
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 ( {this.props.children} ); } }
然后使用它
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 (); }{word}
这就实现了一个可复用的组件,但是还有点复杂,我们还可以优化一下。
Re act Hooks and useEffect
React Hooks 是 React 16.8 中的新特性,它们为 React 组件的生命周期和状态管理提供了一种更简单的方法
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 ( {children} ); }
useEffect
钩子还是有点不一样,它的主要目的是副作用: 改变状态,调用异步函数等等。 在我们的例子中,它根据之前的 visible
的值修改了内部的 noDisplay
布尔值。
将 visible
作为依赖添加到 useEffect
的依赖数组中,当 visible
的值发生变化时, useEffect
钩子才会被调用。
和类组件的杂乱相比较,我认为 useEffect
是一种更好的解决方案。
组件复用: Sidebars 和 Navbars
大家都喜欢 Sidebar 和 Navbar,我们来添加一个吧。
function ToggleButton({ label, isOpen, onClick }) { const icon = isOpen ? ( ) : ( ); return ( ); } function Navbar({ open }) { return ( ); } function Sidebar({ open }) { return (); } function App() { const [navIsOpen, setNavOpen] = useState(false); const [sidebarIsOpen, setSidebarOpen] = useState(false); function toggleNav() { setNavOpen(!navIsOpen); } function toggleSidebar() { setSidebarOpen(!sidebarIsOpen); } return (
- Item 1
- Item 2
- Item 3
); }
还没结束…
到这里我们就可以停下了,但就像我之前提到的关注点分离,我更倾向于避免在 Box
、 Sidebar
和 Navbar
的 render 方法中混合 AnimatedVisibility
组件(代码有点重复)。
我们可以创建一个高阶组件(HOC)。 由于状态管理的原因, HOCs 通常会涉及到类组件。
但是使用了 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 ( ); }; } 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 中使用这些基于函数式的 HOCs
function Navbar() { return ( ); } function Sidebar() { return (); } 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 (
- Item 1
- Item 2
- Item 3
); }
接下来呢?
对于简单的动画,可以使用我所提到的方法。如果比较复杂,我会使用像 react-motion 这样的库。
不仅仅是动画,React Hooks 让我们可以编写可读性高、更简洁的代码。 但是,我们需要在思维上有个调整,像 useEffect 这样的 Hooks 不完全是 React 生命周期函数的替代品,你需要深入学习和研究。
我建议看看像 useHooks.com 这样的网站,还有像 react-use 这样的库(不同钩子用例的集合)。
Happy coding!
非营利组织 freeCodeCamp.org 自 2014 年成立以来,以“帮助人们免费学习编程”为使命,创建了大量免费的 编程教程,包括交互式课程、视频课程、文章等。线下开发者社区遍布 160 多个国家、2000 多个城市。 我们正在帮助全球数百万人学习编程,希望让世界上每个人都有机会获得免费的优质的编程教育资源,成为开发者或者运用编程去解决问题。
你也想成为 freeCodeCamp 社区的贡献者吗?欢迎了解 招募丨freeCodeCamp 翻译计划 。