React
为动画提供一个ReactTransitonGroup
插件组件作为一个底层的动画API
,一个ReactCSSTransitionGroup
来简单地实现基本的CSS
动画和过渡。
1.高级组件:ReactCSSTransitionGroup
(CSS
渐变组)
ReactCSSTransitionGroup
是基于ReactTransitionGroup
的,在React
组件进入或者离开DOM
的时候进行相关处理,它是一种简单地执行CSS
过渡和动画的方式。这个的灵感来自于优秀的ng-animate
库(没用过,估计是angularjs
中的)。
引用组件
import ReactCSSTransitionGroup from 'react-addons-css-transition-group' // ES6
var ReactCSSTransitionGroup = require('react-addons-css-transition-group') // ES5 with npm
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; // ES5 with react-with-addons.js
这个例子让列表项淡入淡出
class TodoList extends React.Component {
constructor(props) {
super(props);
this.state = {items: ['hello','world','click','me']};
this.handleAdd = this.handleAdd.bind(this);
}
handleAdd() {
const newItems = this.state.items.concat([
prompt('Enter some text')
]);
this.setState({items: newItems});
}
handleRemove(i) {
let newItems = this.state.items.slice();
newItems.splice(i,1);
this.setState({items: newItems});
}
render() {
const items = this.state.items.map((item,i) => (
<div key={item} onClick={() => this.handleRemove(i)}>
{item}
</div>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
<ReactCSSTransitionGroup
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{items}
</ReactCSSTransitionGroup>
</div>
);
}
}
注意
你必须为ReactCSSTransitionGroup
的所有的孩子提供key
属性,即便孩子只有一项,也必须给出key
属性,因为React需要依靠它来确定孩子的一些状态,比如插入,移除,或者没有变化。
这里需要提醒的是,如果你是直接在浏览器中使用React
,一定要注意一个问题
react.js
react-with-addons.js
react-dom.js
一定要按照这个顺序引用,至于为什么,后续讲源码的时候就会提到了。
在上面TodoList
这个组件当中,当一个新的项被添加到ReactCSSTransitionGroup
,它将会被添加example-enter
类,然后在下一时刻就会被添加example-enter-active CSS
类。这是一个基于transitionName
属性的约定。
所以我们可以有意识的控制进入和退出的动画,通过class
,如下
.example-enter { opacity: 0.01; }
.example-enter.example-enter-active { opacity: 1; transition: opacity 500ms ease-in; }
.example-leave { opacity: 1; }
.example-leave.example-leave-active { opacity: 0.01; transition: opacity 300ms ease-in; }
2.动画的初始绑定
ReactCSSTransitionGroup
提供了一个可选择的属性transitionAppear
,在组件初始绑定的时候增加额外的过渡阶段,一般transitionAppear
是false
所以在组件初始绑定时没有过渡阶段。
render() {
return (
<ReactCSSTransitionGroup transitionName="example" transitionAppear={true} transitionAppearTimeout={500} transitionEnter={false} transitionLeave={false}>
<h1>Fading at Initial Mount</h1>
</ReactCSSTransitionGroup>
);
}
当初始化的时候,ReactCSSTransitionGroup
会先增加example-appear
,然后再增加 example-appear-active
.example-appear { opacity: 0.01; }
.example-appear.example-appear-active { opacity: 1; transition: opacity .5s ease-in; }
在最开始绑定时,ReactCSSTransitionGroup
所有的孩子都处于appear
状态而没有处于enter
状态,然而,如果孩子加入了一个已经存在的ReactCSSTransitionGroup
组件中去,那么将只有enter
状态而不会经历appear
状态
注意
transitionAppear
在React0.13
版本就已经增加到了ReactCSSTransitionGroup
,为了向后兼容,它的默认值被设置成了false
然而transitionEnter
和transitionLeave
的默认值为true
,所以你必须要为transitionEnterTimeout
和transitionLeaveTimeout
两个属性进行赋值,如果你不需要他们可以通过transitionEnter={false}
和transitionLeave={false}
来关掉他们。
3.自定义动画css-class
我们可以使用自定义的CSS
类名来设计我们过渡,而不是简简单单的使用transitionName=string
来固定死CSS
类名的使用,我们还可以通过一个对象包含提供enter
效果的类名和提供leave
效果的类名,或者是enter
,enter-active
,leave-active
,和leave
效果的类名,如果你仅仅只是提供了enter
和leave
效果的类名,那么React
会自动在这两个类名后面增加'-active'
,以此为结尾。
下面举两个例子
.en {
opacity: 0.01;
}
.en.en-active {
opacity: 1;
transition: opacity 300ms ease-in;
}
.le {
opacity: 1;
}
.le.le-active {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
.ap {
opacity: 0.01;
}
.ap.ap-active {
opacity: 1;
transition: opacity .5s ease-in;
}
// ...
<ReactCSSTransitionGroup
transitionName={ {
enter: 'en',enterActive: 'enActive',leave: 'le',leaveActive: 'leActive',appear: 'ap',appearActive: 'apActive'
} }>
{item}
</ReactCSSTransitionGroup>
<ReactCSSTransitionGroup
transitionName={ {
enter: 'en',appear: 'ap'
} }>
{item2}
</ReactCSSTransitionGroup>
// ...
4.一组动画必须要挂载了才能生效
为了能够给它的孩子也应用过渡效果,ReactCSSTransitionGroup
必须已经挂载到了DOM
[或者属性transitionAppear
被设置为true
:尚不确定]。下面的例子不会生效,因为ReactCSSTransitionGroup
被挂载到新项,而不是新项被挂载到ReactCSSTransitionGroup
里。将这个与上面的高级组件部分比较一下,看看有什么差异。
这里的意思不是说ReactCSSTransitionGroup
不能嵌套,而是说承载ReactCSSTransitionGroup
的容器必须是一个已经挂载到DOM
中的组件,而不是单单的一个React.createElement
创建出来的React
元素。
render() {
const items = this.state.items.map((item,i) => ( <div key={item} onClick={() => this.handleRemove(i)}> <ReactCSSTransitionGroup transitionName="example"> {item} //当然这里存在一些问题,ReactCSSTransitionGroup的孩子必须是一个组件或者DOM //而不能只是文本,虽然文本节点也是DOM,但是在最新版本的React会报错误 //所以尽可能不要在里面直接写文本 </ReactCSSTransitionGroup> </div> )); return ( <div> <button onClick={this.handleAdd}>Add Item</button> {items} </div> ); }
上述代码会报错
5.让一项或者零项动起来
虽然在上面的例子中,我们渲染了一个列表到ReactCSSTransitionGroup
里,然而,ReactCSSTransitionGroup
的孩子可以是一个或零个项目。这使它能够让一个元素实现进入和离开的动画。同样,你可以通过移动一个新的元素来替换当前元素。随着新元素的移入,当前元素移出。例如,我们可以由此实现一个简单的图片轮播:
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
function ImageCarousel(props) {
return (
<div>
<ReactCSSTransitionGroup transitionName="carousel" transitionEnterTimeout={300} transitionLeaveTimeout={300}>
<img src={props.imageSrc} key={props.imageSrc} />
</ReactCSSTransitionGroup>
</div>
);
}
虽然上述的代码出来的效果和性能可能非常懵逼,但是确实是一个不错的例子。
6.禁止动画
如果你想,你可以禁用入场或者出场动画。例如,有些时候,你可能想要一个入场动画,不要出场动画,但是ReactCSSTransitionGroup
会在移除DOM
节点之前等待一个动画完成。你可以给ReactCSSTransitionGroup
添加transitionEnter={false}
或者transitionLeave={false}
来禁用这些动画。
注意
当使用ReactCSSTransitionGroup
的时候,没有办法通知你在过渡效果结束或者在执行动画的时候做一些复杂的运算。如果你想要更多细粒度的控制,你可以使用底层的ReactTransitionGroup API
,该API
提供了你自定义过渡效果所需要的函数。
7.底层的API:ReactTransitionGroup
套路走起
import ReactTransitionGroup from 'react-addons-transition-group' // ES6
var ReactTransitionGroup = require('react-addons-transition-group') // ES5 with npm
var ReactTransitionGroup = React.addons.TransitionGroup; // ES5 with react-with-addons.js
ReactTransitionGroup
是动画的基础。当孩子被添加或者从其中移除(就像上面的例子)的时候,特殊的生命周期函数就会在它们上面被调用(生命周期的概念前面将组件绑定的时候也说了)。
componentWillAppear() componentDidAppear() componentWillEnter() componentDidEnter() componentWillLeave() componentDidLeave()
表现为不同的组件
默认情况下ReactTransitionGroup
渲染成span
标签。你可以通过提供一个component
属性来改变这种行为。
//源码
propTypes: {
component: React.PropTypes.any,childFactory: React.PropTypes.func
},getDefaultProps: function () {
return {
component: 'span',childFactory: emptyFunction.thatReturnsArgument
};
},
以下是你将如何渲染一个<ul>
<ReactTransitionGroup component="ul">
{/* ... */}
</ReactTransitionGroup>
//上述在HTML变为了
<ul>
{/* ... */}
</ul>
需要注意的是ReactTransitionGroup
内部不能文本必须含有至少一个DOM
元素,除此之外,任何额外的、用户定义的属性将会成为已渲染的组件的属性。例如,以下是你将如何渲染一个带有css
类的<ul>
:
<ReactTransitionGroup component="ul" className="animated-list">
{/* ... */}
</ReactTransitionGroup>
<ul class="animated-list">
{/* ... */}
</ul>
每一个React
能渲染的DOM
组件都是可用的。但是,组件并不需要是一个DOM
组件。它可以是任何你想要的React
组件;甚至是你自己已经写好的。直接写成component={List}
,然后再List
组件中你将可以通过this.props.children
来操作ReactTransitionGroup中的孩子
//源码
render: function () {
// TODO: we could get rid of the need for the wrapper node
// by cloning a single child
var childrenToRender = [];
for (var key in this.state.children) {
var child = this.state.children[key];
if (child) {
// You may need to apply reactive updates to a child as it is leaving.
// The normal React way to do it won't work since the child will have
// already been removed. In case you need this behavior you can provide
// a childFactory function to wrap every child,even the ones that are
// leaving.
childrenToRender.push(React.cloneElement(this.props.childFactory(child),{ ref: key,key: key }));
}
}
// 源代码告诉我们是不能通过DOM来获取组件的属性的,因为他们在这里进行了删除。
var props = _assign({},this.props);//创建一个继承于this.props新对象。
delete props.transitionLeave;
delete props.transitionName;
delete props.transitionAppear;
delete props.transitionEnter;
delete props.childFactory;
delete props.transitionLeaveTimeout;
delete props.transitionEnterTimeout;
delete props.transitionAppearTimeout;
delete props.component;
return React.createElement(this.props.component,props,childrenToRender);//这个函数大家应该非常熟悉了。
}
渲染单一的孩子(如果你想的话)
使用ReactTransitionGroup
可以只动态化一个孩子的装载和卸载过程,就像是可伸缩折叠的面板一样,一般ReactTransitionGroup
包含所有的孩子在span
中(或者是之前所说的自定义组件),这是因为很多React
组件都必须只有一个单一的根元素,而ReactTransitionGroup
不再此列中,它没有这个限制,因为它本身就形成一个根元素。
但是如果你只需要一个单一的孩子的话,可以不将它放在`span中,或者其他DOM组件中,直接创建一个自定义的组件返回第一个孩子就可以了。
function FirstChild(props) {
const childrenArray = React.Children.toArray(props.children);
return childrenArray[0] || null;//只返回第一个孩子组件
}
<ReactTransitionGroup component={FirstChild}>
{someCondition ? <MyComponent /> : null}
</ReactTransitionGroup>
这里要注意的是,我们经常用到的组件的props.children
说的是组件内部的DOM
节点,而不是一个文本,也就是说你在一个组件内部只写一个文本props.children
是没有值的。
当然这种处理只能在单一的孩子入场和出场,没有涉及到其他的东时候使用,如果你想实现多个的话,有必要给他们提供给一个公共的DOM
父亲。
也就是说让父亲DOM
作为childrenArray[0]
就可以了。
8.ReactTransitionGroup
函数详解
componentWillAppear()
componentWillAppear()
在TransitionGroup
中,当一个组件进行绑定时,该函数和componentDidMount()
被同时调用,它会阻塞其它动画触发,直到回调函数调用,它只发生在TransitionGroup
初始化渲染时。
componentDidAppear()
componentDidAppear()
该函数在传给componentWillAppear
的回调函数被调用之后调用。
componentWillEnter()
componentWillEnter(callback)
在组件被添加到已有的TransitionGroup
中的时候,该函数和componentDidMount()
被同时调用。它会阻塞其它动画触发,直到回调函数被调用。该函数不会在TransitionGroup
初始化渲染的时候调用。
componentDidEnter()
componentDidEnter()
该函数在传给componentWillEnter
的回调函数被调用之后调用。
componentWillLeave()
componentWillLeave(callback)
该函数在孩子从ReactTransitionGroup
中移除的时候调用。虽然孩子被移除了,但是ReactTransitionGrou
p将会使它继续在DOM
中,直到回调函数被调用,也就是谁必须回调函数被执行后才会将这个元素移除。
componentDidLeave()
componentDidLeave()
该函数在componentWillLeave
回调函数被调用的时候调用(与componentWillUnmount
是同一时间)。
9.隐患
会用渐变组时主要需要注意两点问题:
就我们调用相关函数会延迟子组件的移除比如说
componentWillLeave
函数,当回调函数没有被调用执行完后,不仅动画会被阻塞,连移除动作也会阻塞,因为我们的componentWillLeave
调用则和componentWillUnmount
是同一时刻的,componentWillLeave
没有执行完,componentWillUnmount
便不会结束。必须为每一个子组件设置一个
key
,这个key
要是独一无二的,如果没有设置可能导致动画无法正常的进行。
原文链接:https://www.f2er.com/react/304409.html下一篇将讲
React
的键片段