React(8)-React.addons的常用Mixin

一、React动画Mixin-ReactTransitionGroup

常见的动画分为几大类:CSS动画,JS动画,rAF动画,SVG动画等等

CSS动画:就是给组件的每一个状态先定义好指定的动画类,再在相应的状态加上该类。

JS动画:主要通过setTimeout()和setInterval()通过控制style对应的样式来实现动画,缺点动画时间不精确。

rAF动画:通过requestAnimationFrame()来实现动画,用法和setTimeout差不多,唯一区别是性能做过优化,优点动画时间精确。

此外以上缺点:都有兼容性的问题

下面举例,如何在react中使用动画。

栗子1:CSS动画

<script src="../JS/react-0.14.7/build/react-with-addons.js" type="text/javascript" charset="utf-8"></script>
<script src="../JS/react-0.14.7/build/react-dom.js" type="text/javascript" charset="utf-8"></script>
<style type="text/css">
            .example-enter {
                opacity: 0.01;
            }
            
            .example-enter.example-enter-active {
                opacity: 1;
                transition: opacity 500ms ease-in;
            }
            
            .example-leave {
                opacity: 1;
            }
            
            .example-leave.example-leave-active {
                opacity: 0.01;
                transition: opacity 300ms ease-in;
            }
            .example-appear {
                  opacity: 0.01;
            }

            .example-appear.example-appear-active {
                  opacity: 1;
                  transition: opacity .5s ease-in;
            }
</style>
<div id="app"></div>
<script type="text/babel">
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup; 
var TodoList = React.createClass({
  getInitialState: function() {
    return {items: ['hello', 'world', 'click', 'me']};
  },
  handleAdd: function() {
    var newItems = this.state.items.concat([prompt('Enter some text')]);
    this.setState({items: newItems});
  },
  handleRemove: function(i) {
    var newItems = this.state.items;
    newItems.splice(i, 1);
    this.setState({
        items: newItems
    });
  },
  render: function() {
    var items = this.state.items.map(function(item, i) {
      return (
        <div key={item} ref={item} onClick={this.handleRemove.bind(this, i)}>
          {item}
        </div>
      );
    }.bind(this));
    return (
      <div>
        <button onClick={this.handleAdd}>Add Item</button>
        <ReactCSSTransitionGroup 
            ref = "example"
            transitionName="example" 
            transitionEnterTimeout={500} 
            transitionLeaveTimeout={300}
            transitionAppear={true}
            transitionAppearTimeout={500}
            >
          {items}
        </ReactCSSTransitionGroup>
      </div>
    );
  }
}); 

ReactDOM.render(<TodoList/>, document.getElementById('app'));
</script

栗子2:JS动画

首先,先定义样式

body {
  background: #fff;
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.7;
  margin: 0;
  padding: 30px;
}

a {
  color: #4183c4;
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

code {
  background-color: #f8f8f8;
  border: 1px solid #ddd;
  border-radius: 3px;
  font-family: "Bitstream Vera Sans Mono", Consolas, Courier, monospace;
  font-size: 12px;
  margin: 0 2px;
  padding: 0px 5px;
}

h1, h2, h3, h4 {
  font-weight: bold;
  margin: 0 0 15px;
  padding: 0;
}

h1 {
  border-bottom: 1px solid #ddd;
  font-size: 2.5em;
  font-weight: bold;
  margin: 0 0 15px;
  padding: 0;
}

h2 {
  border-bottom: 1px solid #eee;
  font-size: 2em;
}

h3 {
  font-size: 1.5em;
}

h4 {
  font-size: 1.2em;
}

p, ul {
  margin: 15px 0;
}

ul {
  padding-left: 30px;
}
.exampleT-enter,
.exampleT-leave {
  -webkit-transition: all .25s;
  transition: all .25s;
}

.exampleT-enter,
.exampleT-leave.exampleT-leave-active {
  opacity: 0.01;
}

.exampleT-leave.exampleT-leave-active {
  margin-left: -128px;
}

.exampleT-enter {
  margin-left: 128px;
}

.exampleT-enter.exampleT-enter-active,
.exampleT-leave {
  margin-left: 0;
  opacity: 1;
}

.animateExample {
  display: block;
  height: 128px;
  position: relative;
  width: 384px;
}

.animateItem {
  color: white;
  font-size: 36px;
  font-weight: bold;
  height: 128px;
  line-height: 128px;
  position: absolute;
  text-align: center;
  -webkit-transition: all .25s; /* TODO: make this a move animation */
  transition: all .25s; /* TODO: make this a move animation */
  width: 128px;
}

接着

<script type="text/babel">
      var CSSTransitionGroup = React.addons.CSSTransitionGroup;
      var INTERVAL = 2000;

      var AnimateDemo = React.createClass({
        getInitialState: function() {
          return {current: 0};
        },

        componentDidMount: function() {
          this.interval = setInterval(this.tick, INTERVAL);
        },

        componentWillUnmount: function() {
          clearInterval(this.interval);
        },

        tick: function() {
          this.setState({current: this.state.current + 1});
        },
        render: function() {
          var children = [];
          var pos = 0;
          var colors = ['red', 'gray', 'blue'];
          for (var i = this.state.current; i < this.state.current + colors.length; i++) {
            var style = {
              left: pos * 128,
              background: colors[i % colors.length]
            };
            pos++;
            children.push(<div key={i} className="animateItem" style={style}>{i}</div>);
          }
          return (
            <CSSTransitionGroup
              className="animateExample"
              transitionEnterTimeout={250}
              transitionLeaveTimeout={250}
              transitionName="exampleT">
              {children}
            </CSSTransitionGroup>
          );
        }
      });

      ReactDOM.render(
        <AnimateDemo />,
        document.getElementById('app2')
      );
</script>

上面栗子中使用的setInterval,使用setTimeout也行,如下改动;


componentDidMount:function(){
this.interval = setTimeout(this.tick, INTERVAL);
},
componentWillUnmount: function() {
clearTimeout(this.interval);
},
componentDidUpdate:function(){
this.interval = setTimeout(this.tick, INTERVAL);
},

栗子3:rAF动画


<script type="text/babel">
var CSSTransitionGroup = React.addons.CSSTransitionGroup;
var INTERVAL = 2000;

var AnimateDemo = React.createClass({
getInitialState: function() {
return {current: 0};
},
componentDidMount:function(){
if(this.props.current){
requestAnimationFrame(this.tick);
}
},
componentDidUpdate:function(){
if(this.state.current < this.props.current){
requestAnimationFrame(this.tick);
}
},
tick:function() {
this.setState({
current: this.state.current + 1
});
},
render: function() {
var children = [];
var pos = 0;
var colors = ['red', 'gray', 'blue'];
for (var i = this.state.current; i < this.state.current + colors.length; i++) {
var style = {
left: pos * 128,
background: colors[i % colors.length]
};
pos++;
children.push(<div key={i} className="animateItem" style={style}>{i}</div>);
}
return (
<CSSTransitionGroup
className="animateExample"
transitionEnterTimeout={250}
transitionLeaveTimeout={250}
transitionName="exampleT">
{children}
</CSSTransitionGroup>
);
}
});
var props={
current:100
};
ReactDOM.render(
<AnimateDemo {...props} />,
document.getElementById('dong')
);
</script>

制定类

可以为你的每一步过渡使用制定类名字。代理传递一个字符串到transitionName,你可以传递一个含有enter 或者leave 类名的对象,或者一个含有 enter, enter-active, leave-active, 和 leave 类名的对象。只要提供了enter 和 leave 的类,enter-active 和 leave-active 类会被决定为后缀'-active' 到类名的尾部。这里是使用制定类的例子:

  //jsx中
  <ReactCSSTransitionGroup
    transitionName={ {
      enter: 'enter',
      enterActive: 'enterActive',
      leave: 'leave',
      leaveActive: 'leaveActive',
      appear: 'appear',
      appearActive: 'appearActive'
    } }>
    {item}
  </ReactCSSTransitionGroup>

  <ReactCSSTransitionGroup
    transitionName={ {
      enter: 'enter',
      leave: 'leave',
      appear: 'appear'
    } }>
    {item2}
  </ReactCSSTransitionGroup

在CSS样式中就能对应的使用


.enter{
  margin-left:100px;
}
.enterActive{
  margin-left:0px;
  transition: margin 500ms ease-in; 
}
...

禁用动画

如果你想,你可以禁用 enter 或者 leave 动画。例如,有时你可能想要一个 enter 动画,不要 leave 动画,但是 ReactCSSTransitionGroup 会在移除你的DOM节点之前等待一个动画完成。你可以添加transitionEnter={false} 或者 transitionLeave={false} props 到 ReactCSSTransitionGroup 来禁用这些动画。

底层 API: ReactTransitionGroup

ReactTransitionGroup是动画的基础。它通过 require('react-addons-transition-group') 访问。当子级被声明式的从其中添加或移除(就像上面的例子)时,特殊的生命周期挂钩会在它们上面被调用。

componentWillAppear(callback)

对于被初始化挂载到 TransitionGroup 的组件,它和 componentDidMount() 在相同时间被调用 。它将会阻塞其它动画发生,直到callback被调用。它只会在 TransitionGroup 初始化渲染时被调用。

componentDidAppear()

在 传给componentWillAppear回调 函数被调用后调用。

componentWillEnter(callback)

对于被添加到已存在的 TransitionGroup 的组件,它和 componentDidMount() 在相同时间被调用 。它将会阻塞其它动画发生,直到callback被调用。它不会在 TransitionGroup 初始化渲染时被调用。

componentDidEnter()

在传给 componentWillEnter回调函数被调用之后调用。

componentWillLeave(callback)

在子级从 ReactTransitionGroup 中移除时调用。虽然子级被移除了,ReactTransitionGroup 将会保持它在DOM中,直到callback被调用。

componentDidLeave()

willLeave callback 被调用的时候调用(与 componentWillUnmount 同一时间)。
CSSTransitionGroup

二、React双向绑定Mixin-ReactLink

在React里,数据单向流动: 从拥有者到子级。这是因为数据只单向流动the Von Neumann model of computing。你可以把它想象为 “单向数据绑定”。

然而,有很多应用需要你去读某些数据并回流他们到你的程序。例如,当开发forms,你会常常想更新一些React state 当你收到用户输入的时候。或者也许你想在JavaScript完成布局并相应一些DOM元素大小的变化。

在React里,你可以用监听 "change" 事件来实现它,从你的数据源(通常是DOM)读取并在你的某个组件调用 setState() 。明确的"Closing the data flow loop" 致使了更容易理解和维护的程序。更多信息见our forms documentation.

双向绑定 -- 隐含的强迫DOM里的某些值总是和某些React state 同步 -- 简洁并支持大量多样的应用。 我们提供了 ReactLink:设置如上描述的通用数据回流模式的语法糖,或者 "linking" 某些数据结构到 React state.

举个梨子:

<script type="text/babel">
var LinkedStateMixin = React.addons.LinkedStateMixin;
var WithLink = React.createClass({
  mixins: [LinkedStateMixin],
  getInitialState: function() {
    return {
        message: 'Hello!'
    };
  },
  render: function() {
    return (<div>
        <input type="text" valueLink={this.linkState('message')} />
        <p>{this.state.message}</p>
    </div>);
  }
});

var WithoutLink = React.createClass({
  mixins: [LinkedStateMixin],
  getInitialState: function() {
    return {message: 'Hello!'};
  },
  render: function() {
    var valueLink = this.linkState('message');
    var handleChange = function(e) {
      valueLink.requestChange(e.target.value);
    };
    return (<div>
        <input type="text" value={valueLink.value} onChange={handleChange} />;
        <p>{this.state.message}</p>
    </div>);
  }
});
ReactDOM.render(<WithLink />, document.getElementById('app'));
ReactDOM.render(<WithoutLink />, document.getElementById('app1'));
</script>

在栗子WithoutLin中使用了this.linkState('message').value和this.linkState('message').requestChange()方法,这是这个mixin自带的方法和取值用法。

三、React拷贝组件Mixin-cloneWithProps(React.cloneElement)

拷贝组件,这个组件已经被最新版本弃用,可以改用React.cloneElement代替;

<script type="text/babel">
var cloneWithProps = React.addons.cloneWithProps;

var _makeBlue = function(element) {
  return cloneWithProps(element, {style: {color: 'blue'}});
};

var Blue = React.createClass({
  render: function() {
    var blueChildren = React.Children.map(this.props.children, _makeBlue);
    return (<div>
    {blueChildren}
    {blueChildren}
    </div>);
  }
});

ReactDOM.render(
  <Blue>
    <p>This text is blue.</p>
  </Blue>,
  document.getElementById('app2')
);
</script>

四、React多个节点Mixin-createFragment

在大多数情况下,您可以使用key属性来指定您从渲染返回的元素。然而,这打破了一种情况:如果您需要返回两个子组件,需要使用一个div包裹起来。

<script type="text/babel">

var createFragment = React.addons.createFragment;

var Swapper = React.createClass({
  propTypes: {
    leftChildren: React.PropTypes.node,
    rightChildren: React.PropTypes.node,
    swapped: React.PropTypes.bool
  },
  render: function() {
    var children;
    if (this.props.swapped) {
        children = createFragment({
            right: this.props.rightChildren,
            left: this.props.leftChildren
        });
    } else {
           children = createFragment({
            left: this.props.leftChildren,
            right: this.props.rightChildren
           });
    }
//  if (this.props.swapped) {
//    children = [this.props.rightChildren, this.props.leftChildren];
//  } else {
//    children = [this.props.leftChildren, this.props.rightChildren];
//  }
    return <div>{children}</div>;
  }
});
var props = {
    swapped:false,
    leftChildren:'hello',
    rightChildren:'world'
}
ReactDOM.render(
  <Swapper {...props}></Swapper>,
  document.getElementById('app3')
);
</script>

五、React纯渲染Mixin-PureRenderMixin

PureRenderMixin其实使用shouldComponentUpdate的方法。提高性能!

<script type="text/babel">
var PureRenderMixin = React.addons.PureRenderMixin;
var Pure = React.createClass({
  mixins: [PureRenderMixin],
  render: function() {
    return <div className={this.props.className}>foo</div>;
  }
});
ReactDOM.render(
  <Pure className="hallo"/>,
  document.getElementById('app4')
);
</script>

六、React浅比较Mixin-shallowCompare

shallowCompare 对当前的 propsnextProps对象 执行一个浅的相等检查,同样对于 statenextState对象。 它 通过迭代比较对象的keys 并在 对象的key值不严格相等时返回false 实现此功能.

shallowCompare 返回 true 如果对 props 或 state的浅比较失败,因此组件应该更新。 shallowCompare 返回 false 如果对 props 或 state的浅比较都通过了,因此组件不应该更新。

var shallowCompare = require('react-addons-shallow-compare');
export class SampleComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    return shallowCompare(this, nextProps, nextState);
  }
  render() {
    return <div className={this.props.className}>foo</div>;
  }
}

最后是ReactMixin的全部方法
ReactMixin
其中有两个方法是开发版本才有的:perf和testUtils,有兴趣的伙伴可以下去研究一下。