反应-动态安装和卸载的单一组件

这么简单的事情应该很容易就能完成,但是我却为它的复杂而揪头发。

我想做的只是动画安装和卸载的反应组件,就是这样。以下是我到目前为止所尝试过的方法,以及为什么每种方法都行不通的原因:

  1. ReactCSSTransitionGroup-我根本没有使用 CSS 类,它是所有的 JS 样式,所以这不会工作。
  2. ReactTransitionGroup-这个底层 API 非常棒,但是它需要你在动画完成时使用回调,所以仅仅使用 CSS 转换在这里不起作用。动画库总是存在的,这就引出了下一点:
  3. 绿袜子-许可证对商业用途限制太多 IMO。
  4. 反应动作-这似乎是伟大的,但 TransitionMotion是极其混乱和过于复杂的,我需要什么。
  5. 当然,我可以像材质 UI 那样做一些手脚,元素被渲染但是保持隐藏状态(left: -10000px) ,但是我不想走那条路。我认为这样做不合适,而且我 想要我的组件要卸载,这样它们就可以清理,而不会把 DOM 弄得乱七八糟。

我需要一些 放松来实现。在装载时,动画一组样式; 在卸载时,动画相同(或其他)一组样式。成交。它还必须在多个平台上具有高性能。

我碰壁了。如果我遗漏了什么,有一个简单的方法可以做到这一点,让我知道。

126760 次浏览

这有点长,但是我已经使用了所有的本机事件和方法来实现这个动画。无 ReactCSSTransitionGroupReactTransitionGroup等。

我用过的东西

  • 反应生命周期方法
  • onTransitionEnd事件

这是怎么回事

  • 根据传递的安装支柱(mounted)和默认样式(opacity: 0)安装元素
  • 挂载或更新之后,使用 componentDidMount(进一步更新时使用 componentWillReceiveProps)通过超时(使其异步)更改样式(opacity: 1)。
  • 卸载期间,向组件传递一个道具来标识卸载,再次更改样式(opacity: 0) ,onTransitionEnd,从 DOM 中删除卸载元素。

继续循环。

仔细阅读代码,你就会明白。如果需要任何澄清,请留下评论。

希望这个能帮上忙。

class App extends React.Component{
constructor(props) {
super(props)
this.transitionEnd = this.transitionEnd.bind(this)
this.mountStyle = this.mountStyle.bind(this)
this.unMountStyle = this.unMountStyle.bind(this)
this.state ={ //base css
show: true,
style :{
fontSize: 60,
opacity: 0,
transition: 'all 2s ease',
}
}
}
  

componentWillReceiveProps(newProps) { // check for the mounted props
if(!newProps.mounted)
return this.unMountStyle() // call outro animation when mounted prop is false
this.setState({ // remount the node when the mounted prop is true
show: true
})
setTimeout(this.mountStyle, 10) // call the into animation
}
  

unMountStyle() { // css for unmount animation
this.setState({
style: {
fontSize: 60,
opacity: 0,
transition: 'all 1s ease',
}
})
}
  

mountStyle() { // css for mount animation
this.setState({
style: {
fontSize: 60,
opacity: 1,
transition: 'all 1s ease',
}
})
}
  

componentDidMount(){
setTimeout(this.mountStyle, 10) // call the into animation
}
  

transitionEnd(){
if(!this.props.mounted){ // remove the node on transition end when the mounted prop is false
this.setState({
show: false
})
}
}
  

render() {
return this.state.show && <h1 style={this.state.style} onTransitionEnd={this.transitionEnd}>Hello</h1>
}
}


class Parent extends React.Component{
constructor(props){
super(props)
this.buttonClick = this.buttonClick.bind(this)
this.state = {
showChild: true,
}
}
buttonClick(){
this.setState({
showChild: !this.state.showChild
})
}
render(){
return <div>
<App onTransitionEnd={this.transitionEnd} mounted={this.state.showChild}/>
<button onClick={this.buttonClick}>{this.state.showChild ? 'Unmount': 'Mount'}</button>
</div>
}
}


ReactDOM.render(<Parent />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.3.2/react-with-addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

利用从 Pranesh 的回答中获得的知识,我想出了一个可配置和可重用的替代解决方案:

const AnimatedMount = ({ unmountedStyle, mountedStyle }) => {
return (Wrapped) => class extends Component {
constructor(props) {
super(props);
this.state = {
style: unmountedStyle,
};
}


componentWillEnter(callback) {
this.onTransitionEnd = callback;
setTimeout(() => {
this.setState({
style: mountedStyle,
});
}, 20);
}


componentWillLeave(callback) {
this.onTransitionEnd = callback;
this.setState({
style: unmountedStyle,
});
}


render() {
return <div
style={this.state.style}
onTransitionEnd={this.onTransitionEnd}
>
<Wrapped { ...this.props } />
</div>
}
}
};

用法:

import React, { PureComponent } from 'react';


class Thing extends PureComponent {
render() {
return <div>
Test!
</div>
}
}


export default AnimatedMount({
unmountedStyle: {
opacity: 0,
transform: 'translate3d(-100px, 0, 0)',
transition: 'opacity 250ms ease-out, transform 250ms ease-out',
},
mountedStyle: {
opacity: 1,
transform: 'translate3d(0, 0, 0)',
transition: 'opacity 1.5s ease-out, transform 1.5s ease-out',
},
})(Thing);

最后,在另一个组件的 render方法中:

return <div>
<ReactTransitionGroup>
<Thing />
</ReactTransitionGroup>
</div>

对于那些考虑反应运动,动画一个单一的组成部分时,它安装和卸载可能是压倒性的设置。

有一个名为 返回文章页面【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普】【一分钟科普的库,它使这个过程更容易开始。它是一个反应动作的包装器,这意味着你可以从库中获得所有的好处(例如,你可以中断动画,同时进行多次卸载)。

用法:

import Transition from 'react-motion-ui-pack'


<Transition
enter=\{\{ opacity: 1, translateX: 0 }}
leave=\{\{ opacity: 0, translateX: -100 }}
component={false}
>
{ this.state.show &&
<div key="hello">
Hello
</div>
}
</Transition>

Enter 定义组件的结束状态; leave 是卸载组件时应用的样式。

您可能会发现,一旦您使用过几次 UI 包,响应动作库可能就不再那么令人生畏了。

使用 反应,行动动画化进入和退出过渡要容易得多。

代码和盒子示例

我在工作中遇到过这个问题,虽然看起来很简单,但实际上并不在“反应”中。在一个正常的场景中,呈现如下内容:

this.state.show ? {childen} : null;

随着 this.state.show的变化,孩子们立即被安装/卸载。

我采用的一种方法是创建一个包装器组件 Animate并像这样使用它

<Animate show={this.state.show}>
{childen}
</Animate>

现在随着 this.state.show的变化,我们可以感知道具的变化与 getDerivedStateFromProps(componentWillReceiveProps)和创建中间渲染阶段执行动画。

A stage cycle might look like this

当子节点被安装或卸载时,我们从 静态舞台开始。

一旦我们检测到 show标志的变化,我们就输入 准备阶段,在这里我们从 ReactDOM.findDOMNode.getBoundingClientRect()计算必要的属性,比如 heightwidth

然后输入 动画国家,我们可以使用 css 转换将高度、宽度和不透明度从0改变为计算值(如果卸载则为0)。

在转换结束时,我们使用 onTransitionEnd api 变回 Static台。

关于这些阶段如何平稳过渡还有更多的细节,但这可能是一个全面的想法:)

如果有人感兴趣,我创建了一个 React 库 https://github.com/MingruiZhang/react-animate-mount来分享我的解决方案。欢迎任何反馈:)

下面是我使用新的钩子 API (带有 TypeScript) 基于这篇文章来延迟组件卸载阶段的解决方案:

function useDelayUnmount(isMounted: boolean, delayTime: number) {
const [ shouldRender, setShouldRender ] = useState(false);


useEffect(() => {
let timeoutId: number;
if (isMounted && !shouldRender) {
setShouldRender(true);
}
else if(!isMounted && shouldRender) {
timeoutId = setTimeout(
() => setShouldRender(false),
delayTime
);
}
return () => clearTimeout(timeoutId);
}, [isMounted, delayTime, shouldRender]);
return shouldRender;
}

用法:

const Parent: React.FC = () => {
const [ isMounted, setIsMounted ] = useState(true);
const shouldRenderChild = useDelayUnmount(isMounted, 500);
const mountedStyle = {opacity: 1, transition: "opacity 500ms ease-in"};
const unmountedStyle = {opacity: 0, transition: "opacity 500ms ease-in"};


const handleToggleClicked = () => {
setIsMounted(!isMounted);
}


return (
<>
{shouldRenderChild &&
<Child style={isMounted ? mountedStyle : unmountedStyle} />}
<button onClick={handleToggleClicked}>Click me!</button>
</>
);
}

CodeSandbox 链接。

我的建议是: 感谢@deckele 的解决方案。我的解决方案是基于他的,它是有状态的组件版本,完全可重用。

这是我的沙盒: https://codesandbox.io/s/302mkm1m

在这里,我的剪贴画.js:

import ReactDOM from "react-dom";
import React, { Component } from "react";
import style from  "./styles.css";


class Tooltip extends Component {


state = {
shouldRender: false,
isMounted: true,
}


shouldComponentUpdate(nextProps, nextState) {
if (this.state.shouldRender !== nextState.shouldRender) {
return true
}
else if (this.state.isMounted !== nextState.isMounted) {
console.log("ismounted!")
return true
}
return false
}
displayTooltip = () => {
var timeoutId;
if (this.state.isMounted && !this.state.shouldRender) {
this.setState({ shouldRender: true });
} else if (!this.state.isMounted && this.state.shouldRender) {
timeoutId = setTimeout(() => this.setState({ shouldRender: false }), 500);
() => clearTimeout(timeoutId)
}
return;
}
mountedStyle = { animation: "inAnimation 500ms ease-in" };
unmountedStyle = { animation: "outAnimation 510ms ease-in" };


handleToggleClicked = () => {
console.log("in handleToggleClicked")
this.setState((currentState) => ({
isMounted: !currentState.isMounted
}), this.displayTooltip());
};


render() {
var { children } = this.props
return (
<main>
{this.state.shouldRender && (
<div className={style.tooltip_wrapper} >
<h1 style={!(this.state.isMounted) ? this.mountedStyle : this.unmountedStyle}>{children}</h1>
</div>
)}


<style>{`


@keyframes inAnimation {
0% {
transform: scale(0.1);
opacity: 0;
}
60% {
transform: scale(1.2);
opacity: 1;
}
100% {
transform: scale(1);
}
}


@keyframes outAnimation {
20% {
transform: scale(1.2);
}
100% {
transform: scale(0);
opacity: 0;
}
}
`}
</style>
</main>
);
}
}




class App extends Component{


render(){
return (
<div className="App">
<button onClick={() => this.refs.tooltipWrapper.handleToggleClicked()}>
click here </button>
<Tooltip
ref="tooltipWrapper"
>
Here a children
</Tooltip>
</div>
)};
}


const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

我认为使用 react-transition-group中的 Transition可能是跟踪安装/卸载的最简单方法。它非常灵活。我正在使用一些类来展示它是多么容易使用,但你肯定可以挂接自己的 JS 动画利用 addEndListener道具-我有很多运气使用 GSAP 以及。

沙盒: https://codesandbox.io/s/k9xl9mkx2o

这是我的密码。

import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Transition } from "react-transition-group";
import styled from "styled-components";


const H1 = styled.h1`
transition: 0.2s;
/* Hidden init state */
opacity: 0;
transform: translateY(-10px);
&.enter,
&.entered {
/* Animate in state */
opacity: 1;
transform: translateY(0px);
}
&.exit,
&.exited {
/* Animate out state */
opacity: 0;
transform: translateY(-10px);
}
`;


const App = () => {
const [show, changeShow] = useState(false);
const onClick = () => {
changeShow(prev => {
return !prev;
});
};
return (
<div>
<button onClick={onClick}>{show ? "Hide" : "Show"}</button>
<Transition mountOnEnter unmountOnExit timeout={200} in={show}>
{state => {
let className = state;
return <H1 className={className}>Animate me</H1>;
}}
</Transition>
</div>
);
};


const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

下面是我在2019年制作加载旋转器时解决这个问题的方法,我使用的是 React 函数组件。

我有一个父 应用程序组件,它有一个子 旋转组件。

App 具有应用程序是否加载的状态。当应用程序加载时,旋转正常呈现。当应用程序没有加载(isLoading是假) 旋转是呈现的道具 shouldUnmount

App.js :

import React, {useState} from 'react';
import Spinner from './Spinner';


const App = function() {
const [isLoading, setIsLoading] = useState(false);


return (
<div className='App'>
{isLoading ? <Spinner /> : <Spinner shouldUnmount />}
</div>
);
};


export default App;

Spinner 有状态表示它是否隐藏。在开始时,使用默认的道具和状态,旋转被正常渲染。Spinner-fadeIn类对其进行动画处理。当 旋转接收道具 shouldUnmount时,它使用 Spinner-fadeOut类渲染,动画淡出。

但是我也希望组件在淡出后卸载。

在这一点上,我尝试使用 onAnimationEnd反应合成事件,类似于上面@pranesh-ravi 的解决方案,但它没有工作。相反,我使用 setTimeout将状态设置为隐藏,并延迟与动画相同的长度。旋转将在延迟后用 isHidden === true更新,并且不会呈现任何内容。

这里的关键是父级不卸载子级,它告诉子级什么时候卸载,而子级在完成卸载任务之后卸载自己。

Spinner.js :

import React, {useState} from 'react';
import './Spinner.css';


const Spinner = function(props) {
const [isHidden, setIsHidden] = useState(false);


if(isHidden) {
return null


} else if(props.shouldUnmount) {
setTimeout(setIsHidden, 500, true);
return (
<div className='Spinner Spinner-fadeOut' />
);


} else {
return (
<div className='Spinner Spinner-fadeIn' />
);
}
};


export default Spinner;


Spinner.css:

.Spinner {
position: fixed;
display: block;
z-index: 999;
top: 50%;
left: 50%;
margin: -40px 0 0 -20px;
height: 40px;
width: 40px;
border: 5px solid #00000080;
border-left-color: #bbbbbbbb;
border-radius: 40px;
}


.Spinner-fadeIn {
animation:
rotate 1s linear infinite,
fadeIn .5s linear forwards;
}


.Spinner-fadeOut {
animation:
rotate 1s linear infinite,
fadeOut .5s linear forwards;
}


@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeOut {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}


@keyframes rotate {
100% {
transform: rotate(360deg);
}
}

这可以很容易地使用来自 react-transition-groupCSSTransition组件来完成,它就像您提到的库一样。诀窍在于需要包装 CSSTransfer 组件 没有你通常会有的显示/隐藏机制.ie。否则你隐藏的 动画和它不会工作。例如:

ParentComponent.js


import React from 'react';
import {CSSTransition} from 'react-transition-group';


function ParentComponent({show}) {
return (
<CSSTransition classes="parentComponent-child" in={show} timeout={700}>
<ChildComponent>
</CSSTransition>
)}




ParentComponent.css


// animate in
.parentComponent-child-enter {
opacity: 0;
}
.parentComponent-child-enter-active {
opacity: 1;
transition: opacity 700ms ease-in;
}
// animate out
.parentComponent-child-exit {
opacity: 1;
}
.parentComponent-child-exit-active {
opacity: 0;
transition: opacity 700ms ease-in;
}

框架运动

从 npm 安装 framer-motion。

import { motion, AnimatePresence } from "framer-motion"


export const MyComponent = ({ isVisible }) => (
<AnimatePresence>
{isVisible && (
<motion.div
initial=\{\{ opacity: 0 }}
animate=\{\{ opacity: 1 }}
exit=\{\{ opacity: 0 }}
/>
)}
</AnimatePresence>
)

我知道这里有很多答案,但我仍然没有找到一个适合我的需要。我想要:

  • 功能性组件
  • 这个解决方案允许我的组件在安装/卸载时很容易淡入淡出。

经过几个小时的摆弄,我有了一个可行的解决方案,我敢说90% 可行。我已经在下面的代码中的注释块中编写了这个限制。我仍然喜欢一个更好的解决方案,但这是我找到的最好的解决方案,包括这里的其他解决方案。

const TIMEOUT_DURATION = 80 // Just looked like best balance of silky smooth and stop delaying me.


// Wrap this around any views and they'll fade in and out when mounting /
// unmounting.  I tried using <ReactCSSTransitionGroup> and <Transition> but I
// could not get them to work.  There is one major limitation to this approach:
// If a component that's mounted inside of <Fade> has direct prop changes,
// <Fade> will think that it's a new component and unmount/mount it.  This
// means the inner component will fade out and fade in, and things like cursor
// position in forms will be reset. The solution to this is to abstract <Fade>
// into a wrapper component.


const Fade: React.FC<{}> = ({ children }) => {
const [ className, setClassName ] = useState('fade')
const [ newChildren, setNewChildren ] = useState(children)


const effectDependency = Array.isArray(children) ? children : [children]


useEffect(() => {
setClassName('fade')


const timerId = setTimeout(() => {
setClassName('fade show')
setNewChildren(children)
}, TIMEOUT_DURATION)


return () => {
clearTimeout(timerId)
}


}, effectDependency)


return <Container fluid className={className + ' p-0'}>{newChildren}</Container>
}

如果您有一个组件要淡入/淡出,请将其包装在 <Fade> Ex. <Fade><MyComponent/><Fade>中。

注意,这对类名和 <Container/>使用 react-bootstrap,但是这两者都可以很容易地用定制的 CSS 和常规的旧 <div>替换。

如果我使用 VelocityAnimeJS库直接动画节点(而不是 csssetTimeout) ,那么我发现我可以设计一个 hook提供动画状态 on和功能 onToggle开始动画(例如。滑下,淡出)。

基本上,钩子所做的就是切换动画的开关,然后 之后相应地更新 on。因此,我们可以准确地得到动画的状态。如果不这样做,就会在一个特别的 duration上进行答复。

/**
* A hook to provide animation status.
* @class useAnimate
* @param {object} _                props
* @param {async} _.animate         Promise to perform animation
* @param {object} _.node           Dom node to animate
* @param {bool} _.disabled         Disable animation
* @returns {useAnimateObject}      Animate status object
* @example
*   const { on, onToggle } = useAnimate({
*    animate: async () => { },
*    node: node
*  })
*/


import { useState, useCallback } from 'react'


const useAnimate = ({
animate, node, disabled,
}) => {
const [on, setOn] = useState(false)


const onToggle = useCallback(v => {
if (disabled) return
if (v) setOn(true)
animate({ node, on: v }).finally(() => {
if (!v) setOn(false)
})
}, [animate, node, disabled, effect])


return [on, onToggle]
}


export default useAnimate

用法如下,

  const ref = useRef()
const [on, onToggle] = useAnimate({
animate: animateFunc,
node: ref.current,
disabled
})
const onClick = () => { onToggle(!on) }


return (
<div ref={ref}>
{on && <YOUROWNCOMPONENT onClick={onClick} /> }
</div>
)


生动的实现可以是,

import anime from 'animejs'


const animateFunc = (params) => {
const { node, on } = params
const height = on ? 233 : 0
return new Promise(resolve => {
anime({
targets: node,
height,
complete: () => { resolve() }
}).play()
})
}


你可以使用 反应合成事件

通过像 动画结束过渡结束这样的事件,你可以做到这一点。

反应文件: https://reactjs.org/docs/events.html#animation-events

代码示例: https://dev.to/michalczaplinski/super-easy-react-mount-unmount-animations-with-hooks-4foj

你总是可以使用 React 生命周期方法,但是,无论你使用的是 styled-components还是普通的 css,对于我所遇到的动画来说,response-shift-group 是最方便的库。当您想要跟踪组件的安装和卸载并相应地呈现动画时,它尤其有用。 在使用普通 css 类名时,使用带样式化组件的 TransitionCSSTransition

你可以用 反应过渡小组做到这一点。它提供了 CSS 类,因此您可以在这些 CSS 类中编写动画代码。

遵循这个简单的例子

import {CSSTransition } from 'react-transition-group';//This should be imported
import './AnimatedText.css';


const AnimatedText = () => {
const [showText, setShowText] = useState(false); //By default text will be not shown


//Handler to switch states
const switchHandler = () =>{
setShowText(!showText);
};


return (
//in : pass your state here, it will used by library to toggle. It should be boolean
//timeout: your amination total time(it should be same as mentioned in css)
//classNames: give class name of your choice, library will prefix it with it's animation classes
//unmountOnExit: Component will be unmounted when your state changes to false
<CSSTransition in={showText} timeout={500} classNames='fade' unmountOnExit={true}>
<h1>Animated Text</h1>
</CSSTransition>
<button onClick={switchHandler}>Show Text</button>
);
};


export default AnimatedText;

现在,让我们在 CSS 文件(AnimatedText.CSS)中编写动画,并记住 classNames 属性(在本例中是淡出)

//fade class should be prefixed


/*****Fade In effect when component is mounted*****/
//This is when your animation starts
fade-enter {
opacity: 0;
}


//When your animation is active
.fade-enter.fade-enter-active {
opacity: 1;
transition: all 500ms ease-in;
}
/*****Fade In effect when component is mounted*****/




/*****Fade Out effect when component is unmounted*****/
.fade-exit {
opacity: 1;
}
.fade-exit-active {
opacity: 0;
transition: all 500ms ease-out;
}


/*****Fade Out effect when component is unmounted*****/

还有一个 look 类,可以在组件第一次加载时使用。有关详细信息,请查看文档

如果你正在寻找简单的钩子示例:

import React, { useEffect, useReducer } from "react";
import ReactDOM from "react-dom";


const ANIMATION_TIME = 2 * 1000;


function Component() {
const [isMounted, toggleMounted] = useReducer((p) => !p, true);
const [isAnimateAnmount, toggleAnimateUnmount] = useReducer((p) => !p, false);
const [isVisible, toggleVisible] = useReducer((p) => (p ? 0 : 1), 0);


useEffect(() => {
if (isAnimateAnmount) {
toggleVisible();
toggleAnimateUnmount();
setTimeout(() => {
toggleMounted();
}, ANIMATION_TIME);
}
}, [isAnimateAnmount]);


useEffect(() => {
toggleVisible();
}, [isMounted]);


return (
<>
<button onClick={toggleAnimateUnmount}>toggle</button>
<div>{isMounted ? "Mounted" : "Unmounted"}</div>
{isMounted && (
<div
style=\{\{
fontSize: 60,
opacity: isVisible,
transition: "all 2s ease"
}}
>
Example
</div>
)}
</>
);
}

Edit Animation with unmount

我创建了一个名为 MountAnimation的通用 WrapperComponent,这样您就可以将元素动画化,而不必总是反复编写相同的内容。它使用引擎盖下的 过渡期,所以你需要安装。

  1. 安装依赖项
npm install react-transition-group
  1. 在一个文件夹中创建组件
import { CSSTransition } from "react-transition-group"


export const MountAnimation = ({
children,
timeout = 300, // MATCH YOUR DEFAULT ANIMATION DURATION
isVisible = false,
unmountOnExit = true,
classNames = "transition-translate-y", // ADD YOUR DEFAULT ANIMATION
...restProps
}) => {
return (
<CSSTransition
in={isVisible}
timeout={timeout}
classNames={classNames}
unmountOnExit={unmountOnExit}
{...restProps}
>
<div>{children}</div>
</CSSTransition>
)
}
  1. 像这样简单地使用它:
import { MountAnimation } from '../../path/to/component'


...


const [isElementVisible, setIsElementVisible] = useState(false)


return (
<MountAnimation isVisible={isElementVisible}>
// your content here
</MountAnimation>


)
  1. 你需要在 CSS 文件中声明你的动画。如果要进行代码分割,请确保在全局可用的 CSS 文件中声明这一点。在这个例子中,我使用了以下动画:
.transition-translate-y-enter {
opacity: 0;
transform: translateY(-5px);
}
.transition-translate-y-enter-active {
opacity: 1;
transform: translateY(0px);
transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}
.transition-translate-y-exit {
opacity: 1;
transform: translateY(0px);
}
.transition-translate-y-exit-active {
opacity: 0;
transform: translateY(-5px);
transition: opacity 300ms ease-in-out, transform 300ms ease-in-out;
}

下面是这种实现的一个实例:

Https://codesandbox.io/s/vibrant-elion-ngfzr?file=/src/app.js

重写的 https://stackoverflow.com/a/54114180/8552163

请如果你点击喜欢这个评论,也喜欢家长,谢谢

明白了 Js

import {useState, useEffect} from 'react';


const useDelayUnmount = (isMounted, msDelay = 500)=>{
const [shouldRender, setShouldRender] = useState(false);


useEffect(()=>{
let timeoutId;


if(isMounted && !shouldRender){
setShouldRender(true);
}else if(!isMounted && shouldRender){
timeoutId = setTimeout(()=>setShouldRender(false), msDelay);
}


return ()=>clearTimeout(timeoutId);
}, [isMounted, msDelay, shouldRender]);


return shouldRender;
};


export default useDelayUnmount;

下面的代码与其他答案有些相似,但我想尝试扩展解决方案

  • 方便地添加不同类型的过渡,如淡出,幻灯片等。
  • 使用相同的解决方案而不考虑有条件的渲染或只是无任何条件的渲染。

在下面的代码中,只需要实现这两个代码项

  1. 内部使用 TransitionComponent的反应组件。

    注意: 也可以单独使用 TransitionComponent对不是有条件渲染的组件进行转换。

  2. 和一组 CSS 类。

  3. 休息是如何使用它们。

/**
* This is a simple component which applies your provided trasition on the component.
* Depending upon whether you want to show or hide the component, it adds In and Out classes on the component.
* For example, if your transition name is slide then it will add slideIn and slideOut classes to the component.
* At the end, it also provides onTransitionEnd event property to know when the transition ends.
*/
const TransitionComponent = ({show, transition, onTransitionEnd, children}) => {
const [transitionDirection, setTransitionDirection] = React.useState('out')
  

React.useEffect(() => {
const direction = show ? `${transition}In` : `${transition}Out`
setTransitionDirection(direction)
console.log(`${transition} ${direction}`)
}, [show])
  

return (
<div className={`${transition} ${transitionDirection}`} onTransitionEnd={onTransitionEnd}>
{children}
</div>
)
}


/**
* This can act as base/wrapper component for any custom component to animate
* React Components does not provide you a place to execute some code before a component is unmounted.
* For example, let's say a custom component is conditionally rendered based on a property/expression in the parent component (called as condition) and
* as soon as that condition evaluates to false then component is removed from the DOM.
* What to do when you want to show animations before it is removed? This component handles exactly that.
* This component handles conditional rendering within itself behind mount property and expects you to provide your condition in a show property
* show property is then mapped to the internal mount state at some desired levels like
*  - When show is true, set mount to true. Thereby, adding the component in the DOM and running the animation.
*  - When show is false, check if transition finishes and set mount to false. Thereby, removing the component from the DOM.
* conditionallyRender property? This component comes with another property so that you can enjoy the animations even if you do not want to conditionally render the component
*  - When conditionallyRender is false, mount property is no more in effect and component rendered unconditionally i.e. is not conditionally rendered
*  - When conditionallyRender is true (also default), mount property is in effect to conditionally render the component
*/
const AnimatableComponent = ({conditionallyRender = true, show, transition = 'fade', children}) => {
const [mount, setMount] = React.useState(show);
  

React.useEffect(() => {
if (conditionallyRender && show) {
setMount(true)
console.log('Mounted')
}
}, [show])
  

const handleTransitionEnd = () => {
if (conditionallyRender && !show) {
setMount(false)
console.log('Unmounted')
}
}
  

const getAnimatableChildren = () => {
return (
<TransitionComponent show={show} transition={transition} onTransitionEnd={handleTransitionEnd}>
{children}
</TransitionComponent>
)
}
  

return (
conditionallyRender
? ((show || mount) && getAnimatableChildren())
: getAnimatableChildren()
)
}


const BoxComponent = () => {
return (
<div className='box'>Box</div>
)
}


const App = () => {
const [mountedShow, setMountedShow] = React.useState(false)
const [displayShow, setDisplayShow] = React.useState(false)
  

const handleMountUnmountClick = () => {
setMountedShow(!mountedShow)
}


const handleShowHideClick = () => {
setDisplayShow(!displayShow)
}
  

return (
<React.Fragment>
<div style=\{\{display: 'flex'}}>
<div style=\{\{flex: 1}}>
<h1>Mount/Unmount Transitions</h1>
<AnimatableComponent conditionallyRender={true} show={mountedShow} transition='slide'>
<BoxComponent/>
</AnimatableComponent>
<button type="button" onClick={handleMountUnmountClick}>Mount/Unmount Box</button>
</div>
        

<div style=\{\{flex: 1}}>
<h1>Display Transitions</h1>
<AnimatableComponent conditionallyRender={false} show={displayShow} transition='slide'>
<BoxComponent/>
</AnimatableComponent>
<button type="button" onClick={handleShowHideClick}>Show/Hide Box</button>
</div>
</div>
</React.Fragment>
)
}




ReactDOM.render(<App/>, document.getElementById('app-container'))
.box {
width: 100px;
height: 100px;
border: 1px solid #000;
background-color: #f00;
margin: 24px;
}


.fade {
visibility: hidden;
opacity: 0;
transition: opacity 550ms, visibility 350ms;
}


.fade.fadeIn {
visibility: visible;
opacity: 1;
}




.fade.fadeOut {
visibility: hidden;
opacity: 0;
}


.slide {
visibility: hidden;
opacity: 0;
transform: translateX(100vw);
transition: transform 350ms, opacity 350ms, visibility 350ms;
}




.slide.slideIn {
visibility: visible;
opacity: 1;
transform: translateX(0);
}




.slide.slideOut {
visibility: hidden;
opacity: 0;
transform: translateX(100vw);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
<div id="app-container">This is where React app will initialize</div>

备注:

  • 这是 链接到一个很好的职位,这是提到写这个代码
  • 也参考了这个 代码沙盒的条件渲染,其中 showmount变量都使用了在 ((show || mount) && <Your Component.../>的条件渲染的条件
  • 早些时候,我只使用 mount作为在 mount && <Your Component.../>。我不明白原因,但它似乎与 DOM 重流和 CSS 过渡如何需要一个重流正常工作时,他们是应用于一个有条件渲染的元素,例如元素添加使用 JS 中的 ABC2或 display: none反应 JSX 中的 &&
  • 下面是 其中一个答案的另一个代码盒,它只使用了一个条件,但是这个条件使用了 CSS 动画。CSS 动画似乎没有这个问题,但 CSS 转换有。
  • 在结论中,似乎如果您使用
    • 然后对这两个变量使用逻辑 OR,如 (show || mount) && <Your Component... />中所示。
    • CSS 动画,然后只是在 mountmount && <Your Component... />