最近做的一个 React项目,因为频繁使用动画,考虑到快速开发和更好的可维护性,决定使用其他库来辅助,期间看到 React官网 提供的两个 API,决定尝试一下。
其中高级 API使用起来没什么难度,用过 Angular 或者 Vue 动画的人基本上扫一眼文档就能上手,但是 另外一个 底层API: ReactTransitionGroup,官网上几乎没什么介绍,只将几个生命周期的钩子函数罗列了一下,网上也没有什么关于这方面的讲解或者 Demo之类的东西,基本上都是围绕高级API ReactCSSTransitionGroup的。
因为需要对动画进行细粒度的控制,所以必须要用底层API才行,网上找不到使用的例子,没办法,只好自己亲自上手一个个实验,好在最后差不多算是弄清楚是怎么用的了。
React官网上对底层 API ReactTransitionGroup的解释:
ReactTransitionGroup是动画的基础。它通过 require('react-addons-transition-group)访问。当子级被声`明式的从其中添加或移除(就像上面的例子)时,特殊的生命周期挂钩会在它们上面被调用。
六个特殊的生命周期钩子函数如下:
componentWillAppear() ReactTransitionGroup是动画的基础。它通过require('react-addons-transition-group')访问。当子级被声明式的从其中添加或移除(就像上面的例子)时,特殊的生命周期挂钩会在它们上面被调用。componentDidAppear() 在 传给 componentWillAppear 的 回调 函数被调用后调用。componentWillEnter(callback) 对于被添加到已存在的 TransitionGroup 的组件,它和 componentDidMount() 在相同时间被调用 。它将会阻塞其它动画发生,直到callback被调用。它不会在 TransitionGroup 初始化渲染时被调用。componentDidEnter() 在传给 componentWillEnter的回调函数被调用之后调用。componentWillLeave(callback) 在子级从 ReactTransitionGroup 中移除时调用。虽然子级被移除了,ReactTransitionGroup 将会保持它在DOM中,直到callback被调用。componentDidLeave() 在willLeave callback 被调用的时候调用(与 componentWillUnmount同一时间)相信应该有不少人在看了上述官网讲解后依然是一头雾水,虽然六个钩子函数每一个都说的很仔细,但到底怎么用,可能还是不清楚,至于钩子函数中存在的回调函数 callback 到底是什么东西,就更不明白了。
对于这两点我也是很疑惑,不过实验了一番后,至于弄明白是怎么回事了,先上代码:
// Example.js 运动组件 class Example extends React.Component { componentWillAppear() { console.log('componentWillAppear'); } componentDidAppear() { console.log('componentDidAppear'); } componentWillEnter(callback) { console.log('componentWillEnter'); document.querySelector('.flyBall').className += ' changeTop' callback() } componentDidEnter() { console.log('componentDidEnter'); } componentWillLeave(callback) { callback() } componentDidLeave() { console.log('componentDidLeave'); } render() { return ( <div className="flyBall"></div> ) } }其中 changeTop样式为:
.changeTop { animation: move 5s; } @keyframes move { from {left:0px;} to {left:200px;} } // 引入运动组件 import Example from 'Example' class Main extends React.Component { constructor(props) { super(props) this.state = { show: false } } render() { return ( <div className="box1"> <ReactTransitionGroup component="div"> { this.state.show && <Example/> } </ReactTransitionGroup> <button onClick={this.changeOver.bind(this)}>Click</button> </div> ) } }效果如下:
可以看到,动画的钩子函数,其实也就算是一种特殊的生命周期。
其中, 在本例中, componentWillAppear和 componentDidAppear这两个钩子函数没有执行,这是因为这两个钩子函数与 componentWillEnter 和 componentDidEnter这两个钩子函数是 互斥的,什么意思呢?
componentWillAppear和 componentDidAppear这两个钩子函数,只能在ReactTransitionGroup初始化的时候,被在刚开始就存在于ReactTransitionGroup中的子元素所触发,并且只能触发一次,后来追加进来的子元素只会触发 componentWillEnter 和 componentDidEnter,而不会触发 Enter。
在初始化挂载,所有的ReactCSSTransitionGroup 子级将会 appear 但不 enter。然而,所有后来添加到已存在的 ReactCSSTransitionGroup 的子级将 enter但不 appear。
说的简单点,同一个生命周期中,触发了 componentWillEnter 和 componentDidEnter,那么就不会触发 componentWillAppear和 componentDidAppear,反之亦然。
关于 callback需要说明的是,这东西我开始以为是自己定义的函数,然后再钩子函数中回调调用,但是却一直不明白该怎么将自己写的函数传进去,直到后来实验了几次才明白,原来这东西是内置的参数,根本不用管,如果你把这callback打印出来,就能看到其实它是这样的:
console.log(callback, callback.name) // => "bound" function(){[native code]}是一个函数名为 bound的内置函数(native code)。
这个 callback的作用就是让特殊的生命周期继续往下执行,如果你不调用的话,那么就会阻滞动画的执行。
例如,如果你显式声明了:
componentWillLeave(callback) { // 如果想要动画继续执行,必须调用下面代码,否则元素将被阻滞 callback() }然后如果不在这个函数中执行 callback() 的话,那么元素就会被暂停住,这个时候,就算你的组件正常的生命周期中的 componentWillUnMount 已经执行完了,组件依旧不会被卸载掉。
官网上关于如何使用此 API的说法很模糊,我稍微说一下。
<ReactTransitionGroup component="ul" className="animated-list"> ... </ReactTransitionGroup>官网上给了这种使用方法,如果不指定 component,则默认在页面上渲染出一个 span元素,否则渲染出一个指定的元素,什么意思呢?
例如上述代码渲染出一个 ul元素,并且指定了其类名为 animated-list,其实是说上述代码会被完全替换为 <ul class="animated-list"></ul>这段代码。
你同样可以在其中添加子元素,子元素在渲染的时候会被保留。
<ReactTransitionGroup component="ul" className="animated-list"> <li>a li Element<li> </ReactTransitionGroup>上述会被替换为:
<ul class="animated-list"> <li>a li Element<li> </ul>