ReactJS小记(2)

By | 2015/12/17

上一次我们简单的新建了一个组件,不过这个组件实在太简单了,根本无法把Components的优点表达出来,更主要的是,这个组件完全是静态的,这么一来,我们不如直接写HTML好了呀。

Props属性

props用于定义在新建组件时的属性,在组件的代码中,我们可以使用this.props来获取,光这么说太过抽象,我们来关门放代码……

    <script type="text/babel">
        var CommentBox = React.createClass({
          render: function() {
            return (
              <div className="commentBox">
                Hello, {this.props.name}! I am a CommentBox.
              </div>
            );
          }
        });
        ReactDOM.render(
          <CommentBox name="xishui"></CommentBox>,
          document.getElementById('mount-point')
        );
    </script>

运行它,我们可以看到原来的world被新建组件时传入的xishui所代替(居然想代替世界好大的胆子:-)。

组件的嵌套

在React中,一旦你创建了一个组件,你就可以把它当作一个标准的html标签用了,什么意思呢?我们上面的组件是用标准的<div>创建的,而事实上,我们完全可以使用另外的组件来创建一个新的组件!

        var Comment = React.createClass({
          render: function() {
            return (
              <div className="comment">
                <h2 className="commentAuthor">
                  {this.props.author}
                </h2>
                {this.props.children}
              </div>
            );
          }
        });
        var CommentList = React.createClass({
          render: function() {
            return (
              <div className="commentList">
                <Comment author="Pete Hunt">This is one comment</Comment>
                <Comment author="Jordan Walke">This is *another* comment</Comment>
              </div>
            );
          }
        });
        var CommentForm = React.createClass({
          render: function() {
            return (
              <div className="commentForm">
                <em>I am a CommentForm.</em>
              </div>
            );
          }
        });
        var CommentBox = React.createClass({
          render: function() {
            return (
              <div className="commentBox">
                Hello, {this.props.name}! I am a CommentBox.
                <CommentList></CommentList>
                <CommentForm></CommentForm>
              </div>
            );
          }
        });
        ReactDOM.render(
          <CommentBox name="xishui" />,
          document.getElementById('mount-point')
        );

好复杂好复杂,我们创建了四个组件呢,其中CommentBox用到了CommentList和CommentForm,而CommentList又用到了Comment,这样嵌套的使用组件也是完全么有问题的!事实上你也应该学会这么使用。

  • CommentBox – 最高级的组件,展示了整个留言App的界面
  • CommentList – 留言列表组件,调用Comment组件
  • CommentForm – 留言框组件,用于接收用户输入
  • Comment – 留言组件,用于展示一条留言
20151216151714

执行效果的话,就是这个样子

顺便看看Comment中,我们还用到了一个this.props.children,这是一个特别的属性,代表了书写组件标签内部的所有内容。

事件和状态

我们知道了Components初始化的时候传入的参数可以使用this.props来获得,但是这好像并没有多少改善吧……因为传入的数据不是一成不变的啊,Web页面或者说Web的UI就是要发生改变才有趣嘛。

再给大家介绍一位小兄弟this.state,事实上他可能比this.props更为重要,因为State是保持组件现有数据的一个属性,当你使用setState方法设置state属性的时候,React就会触发刷新UI的动作。如果你想在组件初始化的时候设置一下state,记得使用getInitialState。我们还是用一个例子来说明一下这个东西。

        var CommentBox = React.createClass({
          getInitialState: function () {
              return {count: 2};
          },
          incrCount: function() {
              this.setState({count:this.state.count+1});
          },
          render: function() {
            return (
              <div className="commentBox">
                Hello, {this.props.name}! I am a CommentBox and I have {this.state.count} comments.
                <br />
                <button onClick={this.incrCount}>Click Me</button>
                <CommentList></CommentList>
                <CommentForm></CommentForm>
              </div>
            );
          }
        });

CommentBox又变复杂了一点,我们还增加了一个自定义的方法,用来增加count的值,而为了调用这个方法,使用了很常规的按钮,注意这里的onClick大小写要正确,包括<br />也要正确闭合,因为JSX是一种XML,无法适应html宽松的写法,我们要严格一些才行。

上面的效果就是,点一下按钮,comments的数量就会+1,当然改的只是那个数字,留言本身的条数还没有变化。

其实有件事情一开始出现了,我一直忍住没说……所有涉及到Components的属性或者方法的地方,我们都用了{}来包裹,这也是React的规定,如果不这么写,编译器很难判断这个到底是个表达式还是一个字符串。

有了getInitialState,有没有getInitialProps呢?否则如果新建Components的时候没有传入值想用默认的怎么办?你说的没错,React有这么一个东西,不过名字叫getDefaultProps,其实也很好理解,属性是默认的,状态是初始化后才有的。

除了HTML中的一些事件,React还专门为Components加了几个事件,事实上这也是组件生命周期上几个事件,罗列如下:

  • componentWillMount – 在第一次挂载的时候执行。
  • componentDidMount – 在第一次挂载完成后执行,这是一个用来加载远程数据的好地方。
  • shouldComponentUpdate – 每次重新渲染前执行,可以返回一个false值来阻止UI更新。
  • componentWillUnmount – 在挂载取消前执行。

我们再来为我们的程序引入更多的活力,那个CommentForm不是还空空如也么,就在这里开刀:

        var CommentForm = React.createClass({
          getInitialState: function() {
            return {text:""};
          },
          handleSubmit: function(e) {
            e.preventDefault();
            var text = this.state.text.trim();
            if (text === '') return;
            alert('您输入了【' + text + '】');
          },
          handleTextChange: function(e) {
            this.setState({text:e.target.value});
          },
          render: function() {
            return (
              <div className="commentForm">
                  <form className="commentForm" onSubmit={this.handleSubmit}>
                    <input type="text" placeholder="随便说点啥" value={this.state.text} onChange={this.handleTextChange} />
                    <input type="submit" value="Post" />
                  </form>
              </div>
            );
          }
        });

不得不说,与Angular的双向绑定相比,这个写法还是有点麻烦的……我们为文本框绑定了change事件,每次都捕获用户的输入,然后设置回去;我们同时捕获了表单的提交事件,阻止浏览器的默认提交动作,然后将用户输入的东西打了出来。

再来看看最常用的循环表示一个列表是怎么做的,CommentList里的数据还都是写死的,用一个变量来表示吧:

        var CommentList = React.createClass({
          getInitialState: function() {
            return {comments: ['First comment', 'Second Comment']};
          },
          render: function() {
            return (
              <div className="commentList">
              {
                this.state.comments.map(function(c, i){return <Comment key={i}>{c}</Comment>;})
              }
              </div>
            );
          }
        });

React中对于数据的循环并没有引入一些自定义的方法,而是纯粹是用了JS原生方法,当然如果你对map不熟悉,使用for循环也是可以的(这样的话{}里可以调用一个函数,这个函数的返回值就是拼接的结果)。

有一个注意点是Comment属性里我们加了一个key,这是React推荐的做法,当提供一组循环的组件时,最好为每一个子组件带上唯一的key值,如果你不这么做,React会给出一个警告。我们这里简单的使用了index来作为key,其实不是一个好方法,最好每个comments里每一个元素都有自己的标识(就好像数据表里的主键),不过这里为了简单,我们暂时这么写。

好了,我们都知道下面要做什么了,这么几个组件得有机的结合起来才行——当我们在CommentForm中输入一点什么的时候,我们希望把输入的东西加到CommentList中去。这里牵涉到组件之间的相互关联,我们看看React是怎么解决这个事情的。注意上方的CommentList将数据直接自己保管了,为了通讯上的方便,我们应该把数据交给最高层的CommentBox来管理;而不同层级间方法的调用,我们其实可以使用props值之间传入方法。为了方便对比纠错,把babel代码全部粘上:

var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        {this.props.children}
      </div>
    );
  }
});
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
      {
        this.props.comments.map(function(c, i){return <Comment key={i}>{c}</Comment>;})
      }
      </div>
    );
  }
});
var CommentForm = React.createClass({
  getInitialState: function() {
    return {text:""};
  },
  handleSubmit: function(e) {
    e.preventDefault();
    var text = this.state.text.trim();
    if (text === '') return;
    this.props.handleFormSubmit(text);
    this.setState({text:''});
  },
  handleTextChange: function(e) {
    this.setState({text:e.target.value});
  },
  render: function() {
    return (
      <div className="commentForm">
          <form className="commentForm" onSubmit={this.handleSubmit}>
            <input type="text" placeholder="随便说点啥" value={this.state.text} onChange={this.handleTextChange} />
            <input type="submit" value="Post" />
          </form>
      </div>
    );
  }
});
var CommentBox = React.createClass({
  getInitialState: function () {
      return {comments: ['第一条评论', '第二条评论']};
  },
  handleFormSubmit: function(comment) {
      var comments = this.state.comments;
      comments.push(comment);
      this.setState({comments:comments});
  },
  render: function() {
    return (
      <div className="commentBox">
        你好, {this.props.name}! 我共有{this.state.comments.length}条评论。
        <br />
        <CommentList comments={this.state.comments}></CommentList>
        <CommentForm handleFormSubmit={this.handleFormSubmit}></CommentForm>
      </div>
    );
  }
});
ReactDOM.render(
  <CommentBox name="xishui" />,
  document.getElementById('mount-point')
);

最终效果如下图:

react-comment-box1

好了,现在我们已经学习了React的一些基本概念了,包括JSX,组件,以及一些常见的API。我们最后做了一个本地的Web App(数据都是存放在js变量里的),如果您有一些ajax的基础,可以很容易的把它改写成与服务器同步的应用。

下一次我们来说说React的一些更深层次的东西,大概会是Flux或者React编程思想吧(笑)。

发表评论

您的电子邮箱地址不会被公开。