admin管理员组

文章数量:1426220

I am using ReactCSSTransitionGroup to do some animation and I found an interesting thing which does not make any sense to me.

In the example below, when I click <div className="HeartControl">, it will update the height of the <div className="HeartFill"> which works fine. (I know to achieve the effect does not necessarily need ReactCSSTransitionGroup here though).

Interesting thing is that when I click, there will be another <div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div> with a new React ponent id added after the existing one.

But I expect there will always be only ONE <div className="HeartFill"> there.

Why this happened???

P.S.. after a few clicks, the result will look like:

  <span data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2">
<div class="HeartFill HeartFill-enter HeartFill-enter-active" style="position:absolute;bottom:0;left:0;width:30px;height:3.5999999999999996px;background-color:#D64541;" data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2.$=1$6:0"></div>
<div class="HeartFill HeartFill-enter HeartFill-enter-active" style="position:absolute;bottom:0;left:0;width:30px;height:3px;background-color:#D64541;" data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2.$=1$5:0"></div>

    var HEIGHT_HEART = 30;
   var NUM_HEART_MAX = 50;

   var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;


  var Heart = React.createClass({

getInitialState: function() {
    return {
        heartHeight: 0
    };
},

onClick: function(e) {
    var currentHeartHeight = this.state.heartHeight;
    this.setState({
        heartHeight: currentHeartHeight + 1
    });
},

render: function() {

    var styleHeartFill = {
        'position': 'absolute',
        'bottom': 0,
        'left': 0,
        'width': 30,
        'height': this.state.heartHeight / NUM_HEART_MAX * HEIGHT_HEART,
        'background-color': '#D64541'
    };


    return (
        <div className="Heart" >
                <div className="HeartControl" onClick={this.onClick}>
                    <i className="fa fa-angle-up" />
                </div>
            <img src="heart.png" className="HeartOutline" />
            <ReactCSSTransitionGroup transitionName="HeartFill">
                <div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div>
            </ReactCSSTransitionGroup>
        </div>
    );
 }

     });
    React.renderComponent(<Heart />, document.getElementById('Heart'));

`

I am using ReactCSSTransitionGroup to do some animation and I found an interesting thing which does not make any sense to me.

In the example below, when I click <div className="HeartControl">, it will update the height of the <div className="HeartFill"> which works fine. (I know to achieve the effect does not necessarily need ReactCSSTransitionGroup here though).

Interesting thing is that when I click, there will be another <div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div> with a new React ponent id added after the existing one.

But I expect there will always be only ONE <div className="HeartFill"> there.

Why this happened???

P.S.. after a few clicks, the result will look like:

  <span data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2">
<div class="HeartFill HeartFill-enter HeartFill-enter-active" style="position:absolute;bottom:0;left:0;width:30px;height:3.5999999999999996px;background-color:#D64541;" data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2.$=1$6:0"></div>
<div class="HeartFill HeartFill-enter HeartFill-enter-active" style="position:absolute;bottom:0;left:0;width:30px;height:3px;background-color:#D64541;" data-reactid=".0.4.$8de89f4f1403aee7a963122b06de3712.3.0.0.2.$=1$5:0"></div>

    var HEIGHT_HEART = 30;
   var NUM_HEART_MAX = 50;

   var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;


  var Heart = React.createClass({

getInitialState: function() {
    return {
        heartHeight: 0
    };
},

onClick: function(e) {
    var currentHeartHeight = this.state.heartHeight;
    this.setState({
        heartHeight: currentHeartHeight + 1
    });
},

render: function() {

    var styleHeartFill = {
        'position': 'absolute',
        'bottom': 0,
        'left': 0,
        'width': 30,
        'height': this.state.heartHeight / NUM_HEART_MAX * HEIGHT_HEART,
        'background-color': '#D64541'
    };


    return (
        <div className="Heart" >
                <div className="HeartControl" onClick={this.onClick}>
                    <i className="fa fa-angle-up" />
                </div>
            <img src="heart.png" className="HeartOutline" />
            <ReactCSSTransitionGroup transitionName="HeartFill">
                <div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div>
            </ReactCSSTransitionGroup>
        </div>
    );
 }

     });
    React.renderComponent(<Heart />, document.getElementById('Heart'));

`

Share Improve this question edited Apr 2, 2015 at 9:20 Artjom B. 62k26 gold badges135 silver badges230 bronze badges asked Jul 18, 2014 at 3:18 HaoHao 1,5643 gold badges15 silver badges22 bronze badges
Add a ment  | 

5 Answers 5

Reset to default 2

I suspect the reason your getting more than one is because your using the key prop

<div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div>

From React docs http://facebook.github.io/react/docs/multiple-ponents.html#dynamic-children

When React reconciles the keyed children, it will ensure that any child with key will be reordered (instead of clobbered) or destroyed (instead of reused).

Heres a jsfiddle using the key prop http://jsfiddle/kb3gN/3826/

Heres a jsfiddle not using the key prop http://jsfiddle/kb3gN/3827/

P.s I've made a few changes in the fiddle just to try and better demostrate the reasoning

I'm fairly late to the game with this answer, but I ran into this issue as well and want to provide a solution for others. Removing the key is not a sufficient solution since React relies on it to know when to animate items. The documentation now has a section discouraging this.

You must provide the key attribute for all children of ReactCSSTransitionGroup, even when only rendering a single item. This is how React will determine which children have entered, left, or stayed. - https://facebook.github.io/react/docs/animation.html

If you are only animating the entry/exit of a single item, a CSS hack can be used to fix flickering that may be seen from multiple items entering/exiting.

.HeartFill {
    display: none;
}
.HeartFill:first-child {
    display: block;
}

React will add new elements on top in most cases, but this isn't guaranteed. If your transitionEndTimeout prop is set to a relatively short time, this shouldn't be a huge concern. The timeout prop should also match the CSS transition time.

Here is the problem:

You are providing a value for key which is changing over time. Keys are used to decide if an element is the same or different.

<div key={this.state.heartHeight} className="HeartFill" style={styleHeartFill}></div>

When you do this the value for key changes and React thinks a new element is entering and an old element is leaving.

Usually you need a unique key which can either be sequential or be generated using Math.random(). (remember to generate them once with getInitialState or DefaultProps, not in render, as that would create a new key every time).

The order of elements is another thing that can be in trouble.

From React's documentation:

In practice browsers will preserve property order except for properties that can be
parsed as a 32-bit unsigned integers. Numeric properties will be ordered sequentially
and before other properties. If this happens React will render ponents out of
order. This can be avoided by adding a string prefix to the key:

ReactCSSTransitionGroup should create a second element

it will remove it when a specified css animation has finished or print a warning when there is no animation in the css

maybe look at the Low-level API for better understanding: http://facebook.github.io/react/docs/animation.html (bottom of the page)

I used gluxon's advice as a starting point - what worked for me was removing the leave transition and making it display nothing:

.example-leave.example-leave-active { display: none; }

本文标签: javascriptWhy ReactCSSTransitionGroup generates multiple components though I expect only oneStack Overflow