react hooks使用
Animations delight users. And you’d think, by the sheer volume of articles, that React Hooks delight developers. But for me, fatigue was starting to creep into my opinions on Hooks.
动画使用户满意。 而且,从大量文章中,您会认为React Hooks使开发人员感到高兴。 但是对我来说,疲倦开始逐渐渗透到我对胡克斯的看法上。
But serendipity saved me. I found an example that was a good match for React Hooks, rather than just “the new way.” As you may have guessed by this article’s title, that example was an animation.
但是机缘巧合救了我。 我找到了一个与React Hooks很好匹配的示例,而不仅仅是“新方法”。 正如您可能对本文标题所猜测的那样,该示例是一个动画。
I was working on a React application with cards in a grid. When an item was removed, I wanted to animate its exit, like this.
我正在使用网格中的卡开发React应用程序。 移除某个项目后,我想为其设置动画效果。
Unfortunately, there are nuances to making this work. And my solution led me to a good use of React Hooks.
不幸的是,使这项工作有细微差别。 我的解决方案使我很好地使用了React Hooks。
我们会做什么? (What are We Going to Do?)
- start with a baseline example application 从基线示例应用程序开始
incrementally animate the disappearing of elements, highlighting some challenges
为元素的消失逐步增加动画效果,突出了一些挑战
- once we achieve the desired animation, we’ll refactor a reusable animation component 一旦获得所需的动画,我们将重构可重复使用的动画组件
- we’ll use this component to animate a sidebar and a navbar 我们将使用此组件为侧边栏和导航栏设置动画
- and …. (you need to read / jump to the end) 和...。 (您需要阅读/跳到最后)
For the impatient, here is the GitHub repo for the code in this project. There are tags for each step. (See README for links and descriptions for each tag.)
不耐烦的是,这里是此项目中代码的GitHub存储库 。 每个步骤都有标签。 (有关每个标签的链接和说明,请参见自述文件。)
基准线 (Baseline)
I’ve created a simple application, using create-react-app. It has a grid of simple cards. You can hide individual cards.
我使用create-react-app创建了一个简单的应用程序 。 它具有简单的卡片网格。 您可以隐藏个人卡片。
The code for this is basic and the results are uninteresting. When a user clicks the eye icon button, we change the item’s display
property.
此代码是基本的,结果没有意思。 当用户单击眼睛图标按钮时,我们将更改项目的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>
);
}
(Yes I am using hooks above, but this is not the interesting use of hooks.)
(是的,我在上面使用钩子,但这并不是对钩子的有趣使用。)
添加动画 (Adding Animation)
Rather than build my own animation library, I looked for an animation library like animate.css. react-animated-css is nice library that provides a wrapper around animate.css.
我没有建立自己的动画库,而是寻找诸如animate.css之类的动画库。 React动画的css 是一个不错的库,为animate.css提供了包装。
npm install --save react-animated-css
npm install --save react-animated-css
add animate.css to index.html
将animate.css添加到index.html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.css" />
In the Box
component above, we change it’s rendering to
在上方的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>
);
不完全是我们想要的 (Not quite what we want)
But animate.css animates opacity
and other CSS properties; you can’t do a CSS transition on the display
property. So an invisible object remains and it takes up space in the document flow.
但是animate.css可以设置 opacity
和其他CSS属性的动画。 您不能在display
属性上进行CSS转换。 因此,一个不可见的对象仍然存在,并在文档流中占据了空间。
If you google a bit, you’ll find some solutions that suggest using a timer to set display: none
at the end of the animation.
如果您稍做搜索 ,就会发现一些建议使用计时器设置display: none
解决方案display: none
动画结束时display: none
。
So we can add that,
所以我们可以添加
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>
);
}
(Note: The default animation duration is 1000ms. I use 650ms for the timeout, to minimize a stutter/pause before setting the display
property. This is a matter of preference.)
(注意:默认动画持续时间为1000毫秒。我使用650毫秒作为超时时间,以在设置display
属性之前最大程度地减少卡顿/暂停。这是优先考虑的问题。)
And that will give us the desired effect.
这将给我们带来理想的效果。
创建一个可重用的组件 (Creating a Reusable Component)
We could stop here, but there are two issues (for me):
我们可以在这里停下来,但是有两个问题(对我来说):
I don’t want to copy/paste the
Animated
block, styles and functions to recreate this effect我不想复制/粘贴
Animated
块,样式和函数来重新创建此效果The
Box
component is mixing different kinds of logic, i.e. violating Separation of Concerns. Specifically, theBox
's essential function is to render a card with its content. But the animation details are mixed in.Box
组件混合了各种逻辑,即违反了关注点分离 。 具体来说,Box
的基本功能是渲染带有其内容的卡片。 但是动画细节混在一起。
类组件 (Class Component)
We can create a traditional React class component to manage the state of animation: toggle visibility and set the timeout for the display
CSS property.
我们可以创建一个传统的React类组件来管理动画状态:切换可见性并设置display
CSS属性的超时。
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>
);
}
}
and then use it
然后用它
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>
);
}
This does create a reusable component, but it is a bit complicated. We can do better.
这确实创建了可重用的组件,但是有点复杂。 我们可以做得更好。
React Hooks和useEffect (React Hooks and useEffect)
React Hooks are a new feature in React 16.8. They offer a simpler approach to lifecycle and state management in React components.
React Hooks是React 16.8的新功能。 它们为React组件中的生命周期和状态管理提供了一种更简单的方法。
The useEffect hook provides an elegant replacement to our use of componentWillReceiveProps
. The code is simpler and we can use a functional component again.
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>
);
}
There are some subtleties with the useEffect hook. It’s primarily for side effects: changing state, calling asynchronous functions, etc. In our case, it sets the internal noDisplay
boolean based on the previous value of visible.
useEffect挂钩有一些细微之处。 它主要用于副作用:更改状态,调用异步函数等。在我们的例子中,它根据以前的visible.
值设置内部的noDisplay
布尔值visible.
By adding visible
to the dependencies array for useEffect
, our useEffect
hook will only be called when the value of visible
changes.
通过将visible
添加到useEffect
的依赖项数组中,仅当visible
的值更改时,才会调用我们的useEffect
挂钩。
I think useEffect is a much better solution than the class component clutter. ?
我认为useEffect是比类组件混乱更好的解决方案。 ?
重用组件:边栏和导航栏 (Reusing the Component: Sidebars and Navbars)
Everyone loves a sidebars and navbars. So let’s add one of each.
每个人都喜欢侧边栏和导航栏。 因此,让我们添加一个。
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>
);
}
但是我们还没有完成…… (But We’re Not Done…)
We could stop here. But as with my earlier comments about Separation of Concerns, I’d prefer to avoid mixing the AnimatedVisibility
component in the render method of the Box
, Sidebar
nor Navbar
. (It is also a small amount of duplication.)
我们可以在这里停下来。 但是就像我之前关于关注分离的评论一样,我希望避免在Box
, Sidebar
或Navbar
的render方法中混合AnimatedVisibility
组件。 (这也是少量重复。)
We can create an HOC. (In fact, I wrote an article about animations and HOCs, How to Build Animated Microinteractions in React.) But HOCs usually involve class components, because of the state management.
我们可以创建一个HOC。 (实际上,我写了一篇有关动画和HOC的文章, 如何在React中构建动画微交互 。 )但是,由于状态管理,HOC通常涉及类组件。
But with React Hooks, we can just compose the HOC (functional programming approach).
但是使用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
and then use these function-based HOCs in App.js
然后在App.js
使用这些基于功能的App.js
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>
);
}
At the risk of promoting my own work, I much prefer the clean resulting code.
冒着提升自己的工作的风险,我更喜欢干净的结果代码。
Here is a sandbox of the final result.
这是最终结果的沙盒。
怎么办? (Now What?)
For simple animations, the approach I describe works well. For more complex cases, I would use libraries like react-motion.
对于简单的动画,我描述的方法效果很好。 对于更复杂的情况,我将使用诸如react-motion之类的库。
But separate from animations, React Hooks provide opportunities create readable and simple code. However, there is an adjustment in thinking. Hooks like useEffect are not a straight replacement for all lifecycle methods. You’ll need to study and experiment.
但是与动画分开,React Hooks提供了创建可读和简单代码的机会。 但是,思维有调整。 像useEffect这样的钩子并不是所有生命周期方法的直接替代。 您需要学习和试验。
I suggest looking at sites like useHooks.com and libraries like react-use, a collection of hooks for a variety of use cases.
我建议您查看诸如useHooks.com之类的网站以及诸如react-use的库之类的库,这些库是针对各种用例的钩子集合。
翻译自: https://www.freecodecamp.org/news/animating-visibility-with-css-an-example-of-react-hooks/
react hooks使用