使用阵营TransitionGroup,GSAP和MobX重新呈现相同的组件

问题描述:

我有一个渲染使用各种UI组件的各种问题到屏幕的一项调查显示,等反应中的应用。使用阵营TransitionGroup,GSAP和MobX重新呈现相同的组件

然而,调查的性质是许多问题使用完全相同的部件重新呈现。例如,“A/B选择器”或“清单”。

我想要实现的是每个组件,无论它是第一次被重用还是挂载到DOM,都会从底部淡出 - 一旦用户选择了淡入淡出回答。

下面是使用五个问题一个非常基本的例子,一个小盒子:

import React, {Component, PropTypes} from 'react'; 
import ReactDOM from 'react-dom'; 

import { observer, Provider, inject } from 'mobx-react'; 
import { observable, computed, action } from 'mobx'; 

import 'gsap'; 
import TransitionGroup from 'react-addons-transition-group'; 

// STORE 
class APP_STORE { 

    @observable showingBox = true; 
    @observable transDuration = .25; 

    @observable questionIndex = 0; 

    @observable questions = [ 
    {text: 'one'}, 
    {text: 'two'}, 
    {text: 'three'}, 
    {text: 'four'}, 
    {text: 'five'}, 
    ]; 

    @computed get currentQuestion() { 
    return this.questions[this.questionIndex]; 
    } 

    @action testTrans() { 
    this.showingBox = !this.showingBox; 
    setTimeout(() => { 
     this.questionIndex++; 
     this.showingBox = !this.showingBox; 
    }, this.transDuration * 1000); 
    } 

} 

let appStore = new APP_STORE(); 


// TRANSITION w/HIGHER ORDER COMPONENT 
function fadesUpAndDown (Component) { 
    return (
    class FadesUp extends React.Component { 
     componentWillAppear (callback) { 
     const el = ReactDOM.findDOMNode(this); 
     TweenMax.fromTo(el, appStore.transDuration, {y: 100, opacity: 0}, {y: Math.random() * -100, opacity: 1, onComplete: callback}); 
     } 

     componentWillEnter (callback) { 
     const el = ReactDOM.findDOMNode(this); 
     TweenMax.fromTo(el, appStore.transDuration, {y: 100, opacity: 0}, {y: Math.random() * -100, opacity: 1, onComplete: callback}); 
     } 

     componentWillLeave (callback) { 
     const el = ReactDOM.findDOMNode(this); 
     TweenMax.to(el, appStore.transDuration, {y: 100, opacity: 0, onComplete: callback}); 
     } 

     render() { 
     return <Component ref="child" {...this.props} />; 
     } 
    } 
) 
} 

// REACT 

@fadesUpAndDown 
class Box extends React.Component { 
    render() { 
    return <div className="box" ref={c => this.container = c}> {this.props.data.text} </div>; 
    } 
} 


@inject('store') @observer 
class Page extends Component { 

    render() { 
    const store = this.props.store; 

    return (
     <div className="page"> 
     <TransitionGroup> 
      { store.showingBox ? <Box data={store.currentQuestion}/> : null } 
     </TransitionGroup> 

     <button className="toggle-btn" onClick={() => { store.testTrans(); } } > 
      test trans 
     </button> 
     </div> 
    ); 
    } 

} 

这工作!但是...把它关闭,我必须手动从DOM (通过showingBox观察到在这种情况下)取出盒组件,设置过渡时间超时,和完全重新安装该组件。

最终我猜这很好,但我想知道SO社区中的任何人是否遇到过类似的情况,以更好的方式解决它,因为卸载/重新安装并不是非常强大的React。

RODRIGO原来的答案

你可以尝试使用<TransitionGroup>,一个<Transition>标签为每个组件,并在该标签使用onEnteronExit。这对于一个父组件内几个组件的一种方法:

<TransitionGroup className="col-12"> 
    <Transition 
    key={props.location.pathname} 
    timeout={500} 
    mountOnEnter={true} 
    unmountOnExit={true} 
    onEnter={node => { 
     TweenLite.to(node, 0.5, { 
     autoAlpha: 1, 
     y: Math.random() * -100 
     }); 
    }} 
    onExit={node => { 
     TweenLite.to(node, 0.5, { 
     position: "fixed", 
     autoAlpha: 1, 
     y: 0 
     }); 
    }} 
    /> 
</TransitionGroup>; 

下面是使用这种代码的一个例子,但与反应路由器。显然不是你所追求的,而是使用这种方法的工作示例。转至组件文件夹,到routes.js文件:

https://codesandbox.io/s/mQy3mMznn

唯一需要注意的是,在过渡组配置设定的时间,应该是相同的GSAP实例,以保持挂载/卸载同步,因为onEnteronExitdon¡t提供任何回调。


另一种选择是使用<Transition>元素的addEndListener方法:

<Transition 
    in={this.props.in} 
    timeout={duration} 
    mountOnEnter={true} 
    unmountOnExit={true} 
    addEndListener={(n, done) => { 
    if (this.props.in) { 
     TweenLite.to(n, 1, { 
     autoAlpha: 1, 
     x: 0, 
     ease: Back.easeOut, 
     onComplete: done 
     }); 
    } else { 
     TweenLite.to(n, 1, { autoAlpha: 0, x: -100, onComplete: done }); 
    } 
    }} 
> 
    {state => 
    <div className="card" style={{ marginTop: "10px", ...defaultStyle }}> 
     <div className="card-block"> 
     <h1 className="text-center">FADE IN/OUT COMPONENT</h1> 
     </div> 
    </div>} 
</Transition> 

在这种情况下,方法确实提供done回调可以传递到onComplete处理像角。考虑到这一点,唯一需要注意的是,在过渡组配置的持续时间应比GSAP实例的时间长,否则该组件将被卸载之前的动画完成。如果时间更长并不重要,完成的回调会为您卸载。

这里有一个现场的样品,到组件的文件夹,进入children.js文件:

https://codesandbox.io/s/yvYE9NNW


ZFALEN自己的派生解决方案

罗德里戈的第二个建议,利用从<Transition />组件react-transition-group(请注意,这是而不是react-addons-transition-group相同)最终使我成为一个非常理想的解决方案。

通过在我的MobX存储中使用静态值,我可以声明单个动画持续时间并在其他地方派生它。此外,将<Transition />作为高阶组件函数进行封装,可让我只使用装饰器来指示给定组件应具有哪些动画!

只要我配对的动画与MobX @inject()/<Provider />模式,我基本上只是宣布我GSAP单独的过渡,作为标签所需的相关组件,并控制一切从商店。

这里的原始代码示例(请注意,您将需要有节点纺了支持装饰器的WebPack /通天配置等,还做一些造型,使东西出现。):

import React, {Component, PropTypes} from 'react'; 
import ReactDOM from 'react-dom'; 

import { observer, Provider, inject } from 'mobx-react'; 
import { observable, computed, action } from 'mobx'; 

require('../../../public/stylesheets/transPage.scss'); 

import 'gsap'; 
import Transition from "react-transition-group/Transition"; 

// LIL UTILITY FUNCTIONS 
const TC = require('../../utils/timeConverter'); 

// MOBX STORE 
class APP_STORE { 

    // A TOGGLE & DURATION FOR THE TRANSITION 
    @observable showingBox = true; 
    transDuration = .25; 

    @observable questionIndex = 0; 

    @observable questions = [ 
    {text: 0 }, 
    ]; 

    @computed get currentQuestion() { 
    return this.questions[this.questionIndex]; 
    } 

    @action testTrans() { 

    // TOGGLE THE COMPONENT TO TRANSITION OUT 
    this.showingBox = !this.showingBox; 

    // WAIT UNTIL THE TRANSITION OUT COMPLETES 
    // THEN MAKE CHANGES THAT AFFECT STATE/PROPS 
    // THEN TRANSITION THE COMPONENT BACK IN 
    setTimeout(() => { 

     // IN THIS CASE, ADD A NEW 'QUESTION' TO THE SURVEY ARBITRARILY 
     this.questions.push({text: this.questionIndex + 1 }); 
     this.questionIndex++; 

     this.showingBox = !this.showingBox; 
    }, TC.toMilliseconds(this.transDuration)); 
    } 

} 

let appStore = new APP_STORE(); 


// TRANSITION w/HIGHER ORDER COMPONENT 
function fadesUpAndDown (Component) { 
    return (
    class FadesUp extends React.Component { 

     constructor(props) { 
     super(props); 
     } 

     render() { 
     const store = this.props.store; 

     return (
      <Transition 
      in={store.showingBox} 
      timeout={TC.toMilliseconds(store.transDuration)} 
      mountOnEnter={true} 
      unmountOnExit={true} 
      addEndListener={(n, done) => { 
       if (store.showingBox) { 
       TweenLite.to(n, store.transDuration, { 
        opacity: 1, 
        y: -25, 
        ease: Back.easeOut, 
        onComplete: done 
       }); 
       } else { 
       TweenLite.to(n, store.transDuration, { opacity: 0, y: 100, onComplete: done }); 
       } 
      }} 
      > 
      { state => <Component {...this.props} /> } 
      </Transition> 
     ) 
     } 
    } 
) 
} 

// REACT STUFF 

// JUST TAG COMPONENTS WITH THEIR RELEVANT TRANSITION 
@inject("store") @observer @fadesUpAndDown 
class Box extends React.Component { 

    constructor(props) { 
    super(props); 
    } 

    render() { 
    return <div className="box" > {this.props.data.text} </div> 
    } 
} 


@inject('store') @observer 
class Page extends Component { 

    render() { 
    const store = this.props.store; 

    return (
     <div className="page"> 
     {/* YOU DONT NEED TO EVEN REFERENCE THE TRANSITION HERE */} 
     {/* IT JUST WORKS BASED ON DERIVED MOBX VALUES */} 
     <Box data={store.currentQuestion} /> 

     <button className="toggle-btn" onClick={() => { store.testTrans(); } } > 
      test transition 
     </button> 
     </div> 
    ); 
    } 

} 


ReactDOM.render(
    <Provider store={appStore}> 
    <Page /> 
    </Provider>, 
    document.getElementById('renderDiv') 
); 

if (module.hot) { 
    module.hot.accept(() => { 
    ReactDOM.render(
     <Provider store={appStore}> 
     <Page /> 
     </Provider>, 
     document.getElementById('renderDiv') 
    ) 
    }) 
} 
+0

谢谢,@rodrigo - 第二个例子接近我正在寻找的。实际上,我使用*的'isShowing'状态写了上面非常相似的方法来强制挂载/卸载,无论转换组件是否只是接收新的道具。我会试试看,并回复你 – Zfalen

+1

你是男人。第二种模式起作用了,我可以很好地将它与MobX/Decorators进行很好的结合。查看我的编辑附加到您的答案。 – Zfalen

+0

很高兴帮助;)。也感谢您添加您的解决方案。 MobX在这里并不是一个常见的问题,所以我相信很多人会从你的解决方案中受益。干杯!!! – Rodrigo