摘要:后面將會仔細(xì)分析的源碼實現(xiàn)。更新完成后,對中的每個元素執(zhí)行動畫的邏輯,對中的每個元素執(zhí)行動畫的邏輯。事實上,原因很簡單,事件在某些情況是不會被觸發(fā)。總結(jié)動畫是組件初次后,才會被添加到的所有子元素上。參考資料官方文檔事件
過去一年,React 給整個前端界帶來了一種新的開發(fā)方式,我們拋棄了無所不能的 DOM 操作。對于 React 實現(xiàn)動畫這個命題,DOM 操作已經(jīng)是一條死路,而 CSS3 動畫又只能實現(xiàn)一些最簡單的功能。這時候 ReactCSSTransitionGroup Addon,無疑是一枚強(qiáng)心劑,能夠幫助我們以最低的成本實現(xiàn)例如節(jié)點初次渲染、節(jié)點被刪除時添加動效的需求。本文將會深入實現(xiàn)原理來玩轉(zhuǎn) ReactCSSTransitionGroup。
初窺 ReactCSSTransitionGroup在介紹 ReactCSSTransitionGroup 的用法前,先來實現(xiàn)一個常規(guī) transition 動畫,要實現(xiàn)的是刪除某個節(jié)點的時候,讓該節(jié)點的透明度不斷的變大。
handleRemove(item) { const { items } = this.state; const len = items.length; this.setState({ items: items.reduce((result, entry) => { return entry.id === item.id ? [...result, { ...item, isRemoving: true }] : [...result, item]; }, []) }, () => { setTimeout(() => { this.setState({ items: items.reduce((result, entry) => { return entry.id === item.id ? result : [...result, item]; }, []) }); }, 500); }); }, render() { const items = this.state.items.map((item, i) => { return ({item.name}); }); return (); }{items}
同時我們在 CSS 中需要提供如下的樣式
.removing-item { opacity: 0.01; transition: opacity .5s ease-in; }
相同的需求,使用 ReactCSSTransitionGroup 創(chuàng)建動畫會是怎么的呢?
handleRemove(i) { const { items } = this.state; const len = items.length; this.setState({ items: [...items.slice(0, i), ...item.slice(i + 1, len - 1)] }); }, render() { const items = this.state.items.map((item, i) => { return ({item}); }); return (); }{items}
在這個例子中,當(dāng)新的節(jié)點從 ReactCSSTransitionGroup 中刪除時,這個節(jié)點會被加上 example-leave 的 class,在下一幀中這個節(jié)點還會被加上 example-leave-active 的 class,通過添加以下 CSS 代碼,被刪除的節(jié)點就會有動畫的效果。
.example-leave { opacity: 1; transition: opacity .5s ease-in; } .example-leave.example-leave-active { opacity: 0.01; }
從這個例子,我們可以看到 ReactCSSTransition 可以把開發(fā)者從一大堆動畫相關(guān)的 state 中解放出來,只需要關(guān)心數(shù)據(jù)的變化,以及 CSS 的 transition 動畫邏輯。
后面將會仔細(xì)分析 ReactCSSTransitionGroup 的源碼實現(xiàn)。在看代碼之前,大家可以先看 官網(wǎng)的文檔,對 ReactCSSTransitionGroup 的用法進(jìn)一步了解。看完之中,可以想想兩個問題:
appear 動畫和 enter 動畫有什么區(qū)別?
ReactCSSTransitionGroup 子元素的生命周期是怎樣的?
ReactCSSTransitionGroup 模塊關(guān)系ReactCSSTransitionGroup 的源碼分為5個模塊,我們先看看這5個模塊之間的關(guān)系:
我們來整理一下這幾個模塊的分工與職責(zé):
ReactTransitionEvents 提供了對各種前綴的 transitionend、animationend 事件的綁定和解綁工具
ReactTransitionChildMapping 提供了對 ReactTransitionGroup 這個 component 的 children 進(jìn)行格式化的工具
ReactCSSTransitionGroup 會調(diào)用 ReactCSSTransitionGroupChild 對 children 中的每個元素進(jìn)行包裝,然后將包裝后的 children 作為 ReactTransitionGroup 的 children 。
從這個關(guān)系圖里面可以看到,ReactTransitionGroup 和 ReactCSSTransitionGroupChild 才是實現(xiàn)動畫的關(guān)鍵部分,因此,本文會從 ReactTransitionGroup 開始解讀,然后從 ReactCSSTransitionGroupChild 中解讀怎么實現(xiàn)具體的動畫邏輯。
ReactTransitionGroup 源碼解讀下面我們按照 React 生命周期來解讀 ReactTransitionGroup。
初次 Mount在初始化 state 的時候,將 this.props.children 轉(zhuǎn)化為對象,其中對象的 key 就是 component key,這個 key 與 children 中的元素一一對應(yīng),然后將該對象設(shè)置為 this.state.children;
在初次 render 的時候,將 this.state.children 中每一個普通的 child component 通過指定的 childFactory 包裹成一個新的 component,并渲染成指定類型的 component 的子元素。在下面的源碼中也可以看到,我們在創(chuàng)建過程中給每個 child 設(shè)置的 key 也會作為 ref,方便后續(xù)索引。
render: function() { var childrenToRender = []; for (var key in this.state.children) { var child = this.state.children[key]; if (child) { childrenToRender.push(React.cloneElement( this.props.childFactory(child), {ref: key, key: key} )); } } return React.createElement( this.props.component, this.props, childrenToRender ); }
初次 mount 后,遍歷 this.state.children 中的每個元素,依次執(zhí)行 appear 動畫的邏輯。
更新 component當(dāng)接收到新的 props 后,先將 nextProps.children 和 this.props.children 合并,然后轉(zhuǎn)化為對象,并更新到 this.state.children。計算在 nextProps 中即將 leave 的 child,如果該元素當(dāng)前沒有正在運行的動畫,將該元素的 key 保存在 keysToLeave。
對于 nextProps 中新的 child,如果該元素沒有正在運行的動畫的話(也許會疑惑,一個剛進(jìn)入的元素怎么會有動畫正在運行呢?下文將會解釋),將該元素的 key 保存在 keysToEnter。從這里也能看出來,本來在 nextProps 中即將 leave 的 child 會被保留下來以達(dá)到動畫效果,等動畫效果結(jié)束后才會被 remove。
component 更新完成后,對 keysToEnter 中的每個元素執(zhí)行 enter 動畫的邏輯,對 keysToLeave 中的每個元素執(zhí)行 leave 動畫的邏輯。由于 enter 動畫的邏輯和 appear 動畫的邏輯幾乎一模一樣,無非是變成執(zhí)行 child 的componentWillEnter 和 componentDidEnter 方法。
leave 動畫稍有不同,看下面源碼可以看到,在 leave 動畫結(jié)束后,如果發(fā)現(xiàn)該元素重新 enter,這里會再次執(zhí)行 enter 動畫,否則的話通過更新 state 中的 children 來刪除相應(yīng)的節(jié)點。這里也可以回答,為什么對剛 enter 的元素,也要判斷該元素是否正在進(jìn)行動畫,因為如果該元素上一次 leave 的動畫還沒有結(jié)束,那么這個節(jié)點還一直保留在頁面中運行動畫。
另外,大家有沒有注意到一個問題,如果 leave 動畫的回調(diào)函數(shù)沒有被調(diào)用,那么這個節(jié)點將永遠(yuǎn)不會被移除。
if (currentChildMapping && currentChildMapping.hasOwnProperty(key)) { // This entered again before it fully left. Add it again. this.performEnter(key); } else { this.setState(function(state) { var newChildren = assign({}, state.children); delete newChildren[key]; return {children: newChildren}; }); }
至此,我們看到 ReactTransitionGroup 沒有實現(xiàn)任何具體的動畫邏輯。
ReactCSSTransitionGroup搞清楚 ReactTransitionGroup 的原理以后,ReactCSSTransitionGroup 做的事情就很簡單了。簡單地說, ReactCSSTransitionGroup 調(diào)用了 ReactTransitionGroup ,提供了自己的 childFactory 方法,而這個 childFactory 則是調(diào)用了 ReactCSSTRansitionGroupChild 。
_wrapChild: function(child) { // We need to provide this childFactory so that // ReactCSSTransitionGroupChild can receive updates to name, enter, and // leave while it is leaving. return React.createElement( ReactCSSTransitionGroupChild, { name: this.props.transitionName, appear: this.props.transitionAppear, enter: this.props.transitionEnter, leave: this.props.transitionLeave, appearTimeout: this.props.transitionAppearTimeout, enterTimeout: this.props.transitionEnterTimeout, leaveTimeout: this.props.transitionLeaveTimeout, }, child ); }
下面來看 ReactCSSTransitionGroupChild 是怎么實現(xiàn)節(jié)點的動畫的。以 appear 動畫為例,在 child.componentWillAppear 被調(diào)用的時候,給該節(jié)點加上 xxx-appear 的 className ,并且在一幀(React 里是寫死的17ms)后,給該節(jié)點加上 xxx-appear-active 的 className ,最后在動畫結(jié)束后刪除 xxx-appear 以及 xxx-appear-active 的 className。
enter、leave 動畫的實現(xiàn)類似。到這里源碼就解讀完了,其中,還有一些細(xì)節(jié)要去注意的。
隱藏在 key 里的秘密在源碼解讀的過程中,我們發(fā)現(xiàn) ReactTransitionGroup 會將 children 轉(zhuǎn)化為對象,然后通過 for...in... 遍歷。對于這一過程,會不會感到有所疑慮,ReactTransitionGroup 怎么保證子節(jié)點渲染的順序。
對于這個問題,React 的處理過程可以簡化為下面的代碼,測試結(jié)果顯示,當(dāng) key 為字符串類型時,for...in... 遍歷的順序和 children 的順序能夠保持一致;但是當(dāng) key 為數(shù)值類型時,for...in... 遍歷的順序和 children 的順序就不一定能夠保持一致,大家可以用下面這段簡單的代碼測試一下。
function test (o) { var result = {}; for (var i = 0, len = o.length; i < len; i++) { result[o[i].key] = o[i]; } for (var key in result) { if (result[key]) { console.log(key, result[key]); } } }
因此,我們知道 ReactCSSTransitionGroup 所有子 component 的 key 千萬不要設(shè)置成純數(shù)字,一定要是字符串類型的。
transitionend 之殤在 React 0.14 版本中,React 已經(jīng)表示將在未來的版本中廢棄監(jiān)聽 transitionend、 animationend 事件,而是通過設(shè)置動畫的 timeout 來達(dá)到結(jié)束動畫的目的,有沒有想過 React 為什么要放棄原生事件,而改用 setTimeout。
事實上,原因很簡單,transitontend 事件在某些情況是不會被觸發(fā)。在 transitionend 的 MDN文檔 中有這么幾行文字:
In the case where a transition is removed before completion, such as if the transition-property is removed, then the event will not fire. The event will also not fire if the animated element becomes display: none before the transition fully completes.
當(dāng)動畫元素的 transition 屬性在動畫完成前被移除了,transitionend 事件不會被觸發(fā)
當(dāng)動畫元素在動畫完成前,display 樣式被設(shè)置成 "none",這種情況 transitionend 事件不會被觸發(fā)
當(dāng)動畫還沒完成,當(dāng)前瀏覽器標(biāo)簽頁失焦很長的時間(大于動畫時間),transitionend 事件不會被觸發(fā),直到該標(biāo)簽頁重新聚焦后 transitionend 事件才會觸發(fā)
正是由于 transitionend 不會觸發(fā),會導(dǎo)致隱形 bug,可以看其中一個 bug。
總結(jié)appear 動畫是 ReactCSSTransitionGroup 組件初次 mount 后,才會被添加到 ReactCSSTransitionGroup 的所有子元素上。
enter 動畫是 ReactCSSTransitionGroup 組件更新后,被添加到新增的子元素上。
ReactCSSTransitionGroup 提供創(chuàng)建 CSS 動畫最簡單的方法,對于更加個性化的動畫,大家可以通過調(diào)用 ReactTransitionGroup 自定義動畫。
參考資料React 官方文檔
transitionend 事件
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/78319.html
摘要:為了能夠更好的使用這個工具,今天就對它進(jìn)行一下源碼剖析。它內(nèi)部的關(guān)鍵代碼是在不指定的時候等于,這就意味著的源碼剖析到此結(jié)束,謝謝觀看當(dāng)然如果指定了剖析就還得繼續(xù)。好了,源碼剖析到此結(jié)束,謝謝觀看 React-Redux是用在連接React和Redux上的。如果你想同時用這兩個框架,那么React-Redux基本就是必須的了。為了能夠更好的使用這個工具,今天就對它進(jìn)行一下源碼剖析。 Pr...
摘要:我們先來看下這個函數(shù)的一些神奇用法對于上述代碼,也就是函數(shù)來說返回值是。不管你第二個參數(shù)的函數(shù)返回值是幾維嵌套數(shù)組,函數(shù)都能幫你攤平到一維數(shù)組,并且每次遍歷后返回的數(shù)組中的元素個數(shù)代表了同一個節(jié)點需要復(fù)制幾次。這是我的 React 源碼解讀課的第一篇文章,首先來說說為啥要寫這個系列文章: 現(xiàn)在工作中基本都用 React 了,由此想了解下內(nèi)部原理 市面上 Vue 的源碼解讀數(shù)不勝數(shù),但是反觀...
摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進(jìn)階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...
摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進(jìn)階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...
摘要:歡迎來我的個人站點性能優(yōu)化其他優(yōu)化瀏覽器關(guān)鍵渲染路徑開啟性能優(yōu)化之旅高性能滾動及頁面渲染優(yōu)化理論寫法對壓縮率的影響唯快不破應(yīng)用的個優(yōu)化步驟進(jìn)階鵝廠大神用直出實現(xiàn)網(wǎng)頁瞬開緩存網(wǎng)頁性能管理詳解寫給后端程序員的緩存原理介紹年底補(bǔ)課緩存機(jī)制優(yōu)化動 歡迎來我的個人站點 性能優(yōu)化 其他 優(yōu)化瀏覽器關(guān)鍵渲染路徑 - 開啟性能優(yōu)化之旅 高性能滾動 scroll 及頁面渲染優(yōu)化 理論 | HTML寫法...
閱讀 2112·2023-04-25 17:23
閱讀 2924·2021-11-17 09:33
閱讀 2518·2021-08-21 14:09
閱讀 3602·2019-08-30 15:56
閱讀 2610·2019-08-30 15:54
閱讀 1630·2019-08-30 15:53
閱讀 2136·2019-08-29 13:53
閱讀 1152·2019-08-29 12:31