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',
leave: 'le',
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
要是獨一無二的,如果沒有設置可能導致動畫無法正常的進行。
下一篇將講
React
的鍵片段