React源码剖析系列 - 玩转 React Transition
过去一年,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 ( <div key={item.id} onClick={this.handleRemove.bind(this,item)} className={item.isRemoving ? 'removing-item' : ''}> {item.name} </div> ); }); return ( <div> <button onClick={this.handleAdd}>Add Item</button> <div> {items} </div> </div> ); } 同时我们在 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)] }); },i) => { return ( <div key={item} onClick={this.handleRemove.bind(this,i)}> {item} </div> ); }); return ( <div> <button onClick={this.handleAdd}>Add Item</button> <ReactCSSTransitionGroup transitionName="example"> {items} </ReactCSSTransitionGroup> </div> ); } 在这个例子中,当新的节点从 ReactCSSTransitionGroup 中删除时,这个节点会被加上 .example-leave { opacity: 1; transition: opacity .5s ease-in; } .example-leave.example-leave-active { opacity: 0.01; } 从这个例子,我们可以看到 ReactCSSTransition 可以把开发者从一大堆动画相关的 state 中解放出来,只需要关心数据的变化,以及 CSS 的 transition 动画逻辑。 后面将会仔细分析 ReactCSSTransitionGroup 的源码实现。在看代码之前,大家可以先看 官网的文档,对 ReactCSSTransitionGroup 的用法进一步了解。看完之中,可以想想两个问题:
ReactCSSTransitionGroup 模块关系ReactCSSTransitionGroup 的源码分为5个模块,我们先看看这5个模块之间的关系:
我们来整理一下这几个模块的分工与职责:
从这个关系图里面可以看到,ReactTransitionGroup 和 ReactCSSTransitionGroupChild 才是实现动画的关键部分,因此,本文会从 ReactTransitionGroup 开始解读,然后从 ReactCSSTransitionGroupChild 中解读怎么实现具体的动画逻辑。 ReactTransitionGroup 源码解读下面我们按照 React 生命周期来解读 ReactTransitionGroup。 初次 Mount
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 ); }
更新 component当接收到新的 props 后,先将 对于 nextProps 中新的 child,如果该元素没有正在运行的动画的话(也许会疑惑,一个刚进入的元素怎么会有动画正在运行呢?下文将会解释),将该元素的 key 保存在 keysToEnter。从这里也能看出来,本来在 nextProps 中即将 leave 的 child 会被保留下来以达到动画效果,等动画效果结束后才会被 remove。
component 更新完成后,对 leave 动画稍有不同,看下面源码可以看到,在 leave 动画结束后,如果发现该元素重新 enter,这里会再次执行 enter 动画,否则的话通过更新 另外,大家有没有注意到一个问题,如果 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 动画为例,在
enter、leave 动画的实现类似。到这里源码就解读完了,其中,还有一些细节要去注意的。 隐藏在
|