摘要:后面將會仔細分析的源碼實現。更新完成后,對中的每個元素執行動畫的邏輯,對中的每個元素執行動畫的邏輯。事實上,原因很簡單,事件在某些情況是不會被觸發??偨Y動畫是組件初次后,才會被添加到的所有子元素上。參考資料官方文檔事件
過去一年,React 給整個前端界帶來了一種新的開發方式,我們拋棄了無所不能的 DOM 操作。對于 React 實現動畫這個命題,DOM 操作已經是一條死路,而 CSS3 動畫又只能實現一些最簡單的功能。這時候 ReactCSSTransitionGroup Addon,無疑是一枚強心劑,能夠幫助我們以最低的成本實現例如節點初次渲染、節點被刪除時添加動效的需求。本文將會深入實現原理來玩轉 ReactCSSTransitionGroup。
初窺 ReactCSSTransitionGroup在介紹 ReactCSSTransitionGroup 的用法前,先來實現一個常規 transition 動畫,要實現的是刪除某個節點的時候,讓該節點的透明度不斷的變大。
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 創建動畫會是怎么的呢?
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}
在這個例子中,當新的節點從 ReactCSSTransitionGroup 中刪除時,這個節點會被加上 example-leave 的 class,在下一幀中這個節點還會被加上 example-leave-active 的 class,通過添加以下 CSS 代碼,被刪除的節點就會有動畫的效果。
.example-leave { opacity: 1; transition: opacity .5s ease-in; } .example-leave.example-leave-active { opacity: 0.01; }
從這個例子,我們可以看到 ReactCSSTransition 可以把開發者從一大堆動畫相關的 state 中解放出來,只需要關心數據的變化,以及 CSS 的 transition 動畫邏輯。
后面將會仔細分析 ReactCSSTransitionGroup 的源碼實現。在看代碼之前,大家可以先看 官網的文檔,對 ReactCSSTransitionGroup 的用法進一步了解??赐曛?,可以想想兩個問題:
appear 動畫和 enter 動畫有什么區別?
ReactCSSTransitionGroup 子元素的生命周期是怎樣的?
ReactCSSTransitionGroup 模塊關系ReactCSSTransitionGroup 的源碼分為5個模塊,我們先看看這5個模塊之間的關系:
我們來整理一下這幾個模塊的分工與職責:
ReactTransitionEvents 提供了對各種前綴的 transitionend、animationend 事件的綁定和解綁工具
ReactTransitionChildMapping 提供了對 ReactTransitionGroup 這個 component 的 children 進行格式化的工具
ReactCSSTransitionGroup 會調用 ReactCSSTransitionGroupChild 對 children 中的每個元素進行包裝,然后將包裝后的 children 作為 ReactTransitionGroup 的 children 。
從這個關系圖里面可以看到,ReactTransitionGroup 和 ReactCSSTransitionGroupChild 才是實現動畫的關鍵部分,因此,本文會從 ReactTransitionGroup 開始解讀,然后從 ReactCSSTransitionGroupChild 中解讀怎么實現具體的動畫邏輯。
ReactTransitionGroup 源碼解讀下面我們按照 React 生命周期來解讀 ReactTransitionGroup。
初次 Mount在初始化 state 的時候,將 this.props.children 轉化為對象,其中對象的 key 就是 component key,這個 key 與 children 中的元素一一對應,然后將該對象設置為 this.state.children;
在初次 render 的時候,將 this.state.children 中每一個普通的 child component 通過指定的 childFactory 包裹成一個新的 component,并渲染成指定類型的 component 的子元素。在下面的源碼中也可以看到,我們在創建過程中給每個 child 設置的 key 也會作為 ref,方便后續索引。
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 中的每個元素,依次執行 appear 動畫的邏輯。
更新 component當接收到新的 props 后,先將 nextProps.children 和 this.props.children 合并,然后轉化為對象,并更新到 this.state.children。計算在 nextProps 中即將 leave 的 child,如果該元素當前沒有正在運行的動畫,將該元素的 key 保存在 keysToLeave。
對于 nextProps 中新的 child,如果該元素沒有正在運行的動畫的話(也許會疑惑,一個剛進入的元素怎么會有動畫正在運行呢?下文將會解釋),將該元素的 key 保存在 keysToEnter。從這里也能看出來,本來在 nextProps 中即將 leave 的 child 會被保留下來以達到動畫效果,等動畫效果結束后才會被 remove。
component 更新完成后,對 keysToEnter 中的每個元素執行 enter 動畫的邏輯,對 keysToLeave 中的每個元素執行 leave 動畫的邏輯。由于 enter 動畫的邏輯和 appear 動畫的邏輯幾乎一模一樣,無非是變成執行 child 的componentWillEnter 和 componentDidEnter 方法。
leave 動畫稍有不同,看下面源碼可以看到,在 leave 動畫結束后,如果發現該元素重新 enter,這里會再次執行 enter 動畫,否則的話通過更新 state 中的 children 來刪除相應的節點。這里也可以回答,為什么對剛 enter 的元素,也要判斷該元素是否正在進行動畫,因為如果該元素上一次 leave 的動畫還沒有結束,那么這個節點還一直保留在頁面中運行動畫。
另外,大家有沒有注意到一個問題,如果 leave 動畫的回調函數沒有被調用,那么這個節點將永遠不會被移除。
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 沒有實現任何具體的動畫邏輯。
ReactCSSTransitionGroup搞清楚 ReactTransitionGroup 的原理以后,ReactCSSTransitionGroup 做的事情就很簡單了。簡單地說, ReactCSSTransitionGroup 調用了 ReactTransitionGroup ,提供了自己的 childFactory 方法,而這個 childFactory 則是調用了 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 是怎么實現節點的動畫的。以 appear 動畫為例,在 child.componentWillAppear 被調用的時候,給該節點加上 xxx-appear 的 className ,并且在一幀(React 里是寫死的17ms)后,給該節點加上 xxx-appear-active 的 className ,最后在動畫結束后刪除 xxx-appear 以及 xxx-appear-active 的 className。
enter、leave 動畫的實現類似。到這里源碼就解讀完了,其中,還有一些細節要去注意的。
隱藏在 key 里的秘密在源碼解讀的過程中,我們發現 ReactTransitionGroup 會將 children 轉化為對象,然后通過 for...in... 遍歷。對于這一過程,會不會感到有所疑慮,ReactTransitionGroup 怎么保證子節點渲染的順序。
對于這個問題,React 的處理過程可以簡化為下面的代碼,測試結果顯示,當 key 為字符串類型時,for...in... 遍歷的順序和 children 的順序能夠保持一致;但是當 key 為數值類型時,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 千萬不要設置成純數字,一定要是字符串類型的。
transitionend 之殤在 React 0.14 版本中,React 已經表示將在未來的版本中廢棄監聽 transitionend、 animationend 事件,而是通過設置動畫的 timeout 來達到結束動畫的目的,有沒有想過 React 為什么要放棄原生事件,而改用 setTimeout。
事實上,原因很簡單,transitontend 事件在某些情況是不會被觸發。在 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.
當動畫元素的 transition 屬性在動畫完成前被移除了,transitionend 事件不會被觸發
當動畫元素在動畫完成前,display 樣式被設置成 "none",這種情況 transitionend 事件不會被觸發
當動畫還沒完成,當前瀏覽器標簽頁失焦很長的時間(大于動畫時間),transitionend 事件不會被觸發,直到該標簽頁重新聚焦后 transitionend 事件才會觸發
正是由于 transitionend 不會觸發,會導致隱形 bug,可以看其中一個 bug。
總結appear 動畫是 ReactCSSTransitionGroup 組件初次 mount 后,才會被添加到 ReactCSSTransitionGroup 的所有子元素上。
enter 動畫是 ReactCSSTransitionGroup 組件更新后,被添加到新增的子元素上。
ReactCSSTransitionGroup 提供創建 CSS 動畫最簡單的方法,對于更加個性化的動畫,大家可以通過調用 ReactTransitionGroup 自定義動畫。
參考資料React 官方文檔
transitionend 事件
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/78319.html
摘要:為了能夠更好的使用這個工具,今天就對它進行一下源碼剖析。它內部的關鍵代碼是在不指定的時候等于,這就意味著的源碼剖析到此結束,謝謝觀看當然如果指定了剖析就還得繼續。好了,源碼剖析到此結束,謝謝觀看 React-Redux是用在連接React和Redux上的。如果你想同時用這兩個框架,那么React-Redux基本就是必須的了。為了能夠更好的使用這個工具,今天就對它進行一下源碼剖析。 Pr...
摘要:我們先來看下這個函數的一些神奇用法對于上述代碼,也就是函數來說返回值是。不管你第二個參數的函數返回值是幾維嵌套數組,函數都能幫你攤平到一維數組,并且每次遍歷后返回的數組中的元素個數代表了同一個節點需要復制幾次。這是我的 React 源碼解讀課的第一篇文章,首先來說說為啥要寫這個系列文章: 現在工作中基本都用 React 了,由此想了解下內部原理 市面上 Vue 的源碼解讀數不勝數,但是反觀...
摘要:歡迎來我的個人站點性能優化其他優化瀏覽器關鍵渲染路徑開啟性能優化之旅高性能滾動及頁面渲染優化理論寫法對壓縮率的影響唯快不破應用的個優化步驟進階鵝廠大神用直出實現網頁瞬開緩存網頁性能管理詳解寫給后端程序員的緩存原理介紹年底補課緩存機制優化動 歡迎來我的個人站點 性能優化 其他 優化瀏覽器關鍵渲染路徑 - 開啟性能優化之旅 高性能滾動 scroll 及頁面渲染優化 理論 | HTML寫法...
閱讀 2101·2023-04-25 17:23
閱讀 2919·2021-11-17 09:33
閱讀 2513·2021-08-21 14:09
閱讀 3579·2019-08-30 15:56
閱讀 2605·2019-08-30 15:54
閱讀 1623·2019-08-30 15:53
閱讀 2126·2019-08-29 13:53
閱讀 1141·2019-08-29 12:31