用React编写组件
by addy 原创文章,欢迎转载,但希望全文转载,注明本文地址。
去年年底非常热门的前端议题就是facebook的开源库react.js,还有尚未开源的react native也带来了一丝神秘感,下面就简单说下用react写组件的过程和心得。
如果你不想听我啰嗦,直接看示例看源码就点击下面
demo。文章大体是根据facebook的文档翻译过来的,英文水平有限,有点捉急,另外写了一个tab组件。
开始
用你最喜欢的编辑器创建一个新的html文件,代码如下:
<html>
<head>
<title>苟富贵</title>
<script src="https://fb.me/react-0.13.0.js"></script>
<script src="https://fb.me/JSXTransformer-0.13.0.js"></script>
<script src="https://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/jsx">
// 在这里写你的代码
</script>
</body>
</html>
组件的结构
组件的结构如下:CommentBox是整体组件,分为回复列表(CommentList)和表单(CommentForm),回复列表(CommentList)包含回复项(Comment)。
- CommentBox
- CommentList
- Comment
- CommentForm
先来编写第一个组件:
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
React.render(
<CommentBox />,
document.getElementById('content')
);
需要注意的是上面这段代码写在<script type="text/jsx">
标签里面,JSXTransformer会将里面的代码转换为正常的javascript代码。可以在线转换,也可以离线做好转换。html和javascript代码混合着写UI层,乍看觉得变扭,实则带来了便利,在编码中能够体会得到。
在创建新组件时,调用的是React.createClass()
方法,传入一个javascript对象,最重要的一个是render
方法,返回的React组件最终渲染为HTML。render方法里面的<div>
标签不是真正的DOM节点,而是React的div
组件的实例。由于React的渲染过程不是HTML字符串的拼接,默认就避免了XSS漏洞,因此React是安全的。
因为class
在javascript中保留的关键字,所有样式的名称必须赋值给className
。
Return返回的内容也不一定得是最基本的HTML标签,还可以是现成的React组件,组件之间可以随意组合。组件可组合是前端的一条重要原则。
React.render()
实例化根组件,框架的入口函数。
组合组件
接下来新建两个组件CommentList
和 CommentForm
,从最简单的div
开始
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
Hello, world! I am a CommentList.
</div>
);
}
});
var CommentForm = React.createClass({
render: function() {
return (
<div className="commentForm">
Hello, world! I am a CommentForm.
</div>
);
}
});
然后框架更新CommentBox
组件
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList />
<CommentForm />
</div>
);
}
});
注意,现在HTML标签和React组件混合在一起构建。HTML组件和正规的React组件没什么区别,在JSX编译的时候,会调用React.createElement(tagName)
写HTML,表达式等其他的东西会单独处理,也规避了全局变量的污染。
使用props
然后我们要创建Comment
组件,组件的数据依赖父组件。通过父组件的property变量传递数据,这里有点类似DOM节点的属性。组件的值通过this.props
取得父组件的值。
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
author 是Comment组件定义的属性,这个值通常是外部传递进来的。{}花括号中可以包含javascript表达式,Comment定义的任何属性都会挂靠在this.props
上。React还提供了this.props.children
来访问子组件。来看下使用的实例:
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>
);
}
});
数据模型
下面的例子中,数据是直接写死的,通常我们的业务中数据是来自server端,也就是我们通过CGI请求去获取得到。假设返回的数据结构是一个json:
var data = [
{author: "Pete Hunt", text: "This is one comment"},
{author: "Jordan Walke", text: "This is *another* comment"}
];
现在我们要把组件和数据联系起来,CommentList
是负责展示回复数据的组件,可以利用props来获取到data。改造下代码:
// tutorial10.js
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function (comment) {
return (
<Comment author={comment.author}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
{commentNodes}
</div>
);
}
});
通过this.props.data
获取到父组件传进来的数据,所以data 是 CommentList组件的一个属性:
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.props.data} />
<CommentForm />
</div>
);
}
});
React.render(
<CommentBox data={data} />,
document.getElementById('content')
);
这里可以看到data数据是CommentBox
组件的属性,用this.props.data
传递给CommentList
组件。data指向前面定义好的json数据,渲染数据这块就基本做好了。
从服务端获取数据
现在换一种方式获取数据:
React.render(
<CommentBox url="comments.php" />,
document.getElementById('content')
);
定义一个url属性,通过该url获取回复的数据。设置一个定时查询,组件就可以自渲染,当有新的回复时,就能渲染新的回复,而且是增量更新,这个得益于React的diff算法,还没有仔细研究源码,有机会可以分析下。
组件状态
前面写的组件能够通过定义的props属性渲染一次,可props是不变的,他们是通过父组件传递过来的,数据是属于父组件。为了实现互动,React引入了一个可变的state状态。this.state
对组件是私有的,通过this.setState()
改变它的值。当state更新了,组件会自渲染。这样输入可以直接体现在UI的更新上。
下面改写下代码,给CommentBox
组件加上状态:
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
getInitialState()
在组件的生命周期内只会执行一次,就是初始化组件的状态值,这里设置了一个data空数组,然后可以调用this.state.data
来获得当前的状态。
更新state
数据通过ajax异步从server获得,获取数据后通过this.setState()
改变组件状态,下面继续改代码:
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
componentDidMount是React内部定义的一个方法,在组件渲染的时候会自动调用一次。加一个定时更新的功能:
// tutorial14.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
React.render(
<CommentBox url="comments.json" pollInterval={2000} />,
document.getElementById('content')
);
回复组件
用户的输入,并且保存。让组件变得可交互,用户提交表单后,清空表单,提交到server,刷新回复列表;
var CommentForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var author = this.refs.author.getDOMNode().value.trim();
var text = this.refs.text.getDOMNode().value.trim();
if (!text || !author) {
return;
}
// TODO: send request to the server
this.refs.author.getDOMNode().value = '';
this.refs.text.getDOMNode().value = '';
return;
},
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
<input type="text" placeholder="Your name" ref="author" />
<input type="text" placeholder="Say something..." ref="text" />
<input type="submit" value="Post" />
</form>
);
}
});
通过onSubmit 属性绑定事件。值得注意的是Refs这个属性,做为子组件的一个name属性标记,通过this.refs.text.getDOMNode()
获得浏览器原生的DOM节点。
还遗留了一点,提交请求到server,为了组件的通用性,最好的办法当然不要将数据提交放在这里。不过可以执行一个回调函数,关键是回调函数该怎么传递给组件呢?
答案是props
属性。
// tutorial17.js
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
handleCommentSubmit: function(comment) {
// TODO: submit to the server and refresh the list
},
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
this.loadCommentsFromServer();
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
});
在CommentForm
组件中调用
this.props.onCommentSubmit({author: author, text: text});
handleCommentSubmit 就是提交请求了
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
data: comment,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
如果提交成功会执行this.setState({data: data});
更新组件的状态,然后绘制UI。为了让用户感知的更快一点,不等到请求完成后更新,也可以直接更新UI,同时提交到server。
handleCommentSubmit: function(comment) {
var comments = this.state.data;
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.ajax({
// .....
})
}
恭喜,看到这里组件编写的过程基本完成。
总结
以下几点在学习的时候值得注意:
1、React 的API比较少也比较简单,但入门也并不轻松,主要是打破了一些固有思维。
2、主要理解state,props作为React的状态和属性等机制。
3、完全用不上事件代理,还是挺颠覆传统前端开发思维。
4、组件间的通信,前面看到了,可以通过callback,但如果再隔一级,或者平级的话,就不灵通了。复杂的情况甚至有多个组件。
5、不要把React当jQuery用,比如:$(‘xxx’).click(doSomething),这是典型的库思维。
6、不要把React当模板引擎用。
本文为原创文章,可能会经常更新知识点以及修正一些错误,因此转载请保留原出处,方便溯源,谢谢合作
个人知乎,欢迎关注:https://www.zhihu.com/people/iamaddy
欢迎关注公众号【入门游戏开发】
翻译的不错