使用阵营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>
标签为每个组件,并在该标签使用onEnter
和onExit
。这对于一个父组件内几个组件的一种方法:
<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实例,以保持挂载/卸载同步,因为onEnter
和onExit
don¡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')
)
})
}
谢谢,@rodrigo - 第二个例子接近我正在寻找的。实际上,我使用顶级的'isShowing'状态写了上面非常相似的方法来强制挂载/卸载,无论转换组件是否只是接收新的道具。我会试试看,并回复你 – Zfalen
你是男人。第二种模式起作用了,我可以很好地将它与MobX/Decorators进行很好的结合。查看我的编辑附加到您的答案。 – Zfalen
很高兴帮助;)。也感谢您添加您的解决方案。 MobX在这里并不是一个常见的问题,所以我相信很多人会从你的解决方案中受益。干杯!!! – Rodrigo