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