上一次我们简单的新建了一个组件,不过这个组件实在太简单了,根本无法把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 – 留言组件,用于展示一条留言
顺便看看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的一些基本概念了,包括JSX,组件,以及一些常见的API。我们最后做了一个本地的Web App(数据都是存放在js变量里的),如果您有一些ajax的基础,可以很容易的把它改写成与服务器同步的应用。
下一次我们来说说React的一些更深层次的东西,大概会是Flux或者React编程思想吧(笑)。