React入门
这里介绍一篇react网站上的教程,介绍如何使用react组件。这个组件包含以下一些内容
1.评论里面所有的视图
2.一个用来提交评论的表单
3.一个自定义的后台的钩子
原地址点这里
开始
源码里面引用的一些资源均是在facebook的CDN上面,可以直接访问,也可以自己去下载这些引用的资源。现在打开编辑器,创建一个html文档。
<!-- index.html -->
<html>
<head>
<title>Hello React</title>
<script src="http://fb.me/react-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
<script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/jsx">
// Your code here
</script>
</body>
</html>
我们这里将javascript代码写在script标签里面(注意看上面script标签的type属性)
上面的html文档中有使用jquery,这里仅仅只是为了方便调用ajax而已,实际上react是可以脱离jquery正常运行的。
第一个组件
React创建出来的组件,是模块化的、易于组合的组件。比如我们接下来要介绍的评论组件,它的结构如下
- CommentBox
- CommentList
- Comment
- CommentForm
首先,我们创建一个CommentBox组件,这个组件就是一个简单的div
// tutorial1.js
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
Hello, world! I am a CommentBox.
</div>
);
}
});
React.render(
<CommentBox />,
document.getElementById('content')
);
JSX语法
可以看到上面的代码中使用了奇怪的语法(类似xml的语法),facebook提供一个简单的预编译方式,可以将这种语法的代码转化成普通的javascript代码
// tutorial1-raw.js
var CommentBox = React.createClass({displayName: ‘CommentBox’,
render: function() {
return (
React.createElement(‘div’, {className: “commentBox”},
“Hello, world! I am a CommentBox.”
)
);
}
});
React.render(
React.createElement(CommentBox, null),
document.getElementById(‘content’)
);
在代码中使用jsx语法是可选的,使用这种语法是会让你更轻松的编写出来更直观的react风格的代码。但是说实话,这种写法上手有的复杂,用过几次就会觉得不错了。jsx具体内容参考这里
分析如上代码会发现,这里我们传入一些方法给React.createClass(),然后创建出来了React组件,这里面最重要的方法就是render方法了,render方法返回一个树状的React组件集合,最终这些组件被渲染到html文档上。
div标签并不是DOM节点,它是React里面的组件。它只能被React识别的,你可以把它当做一个特殊的标记或者数据。在这里,React是很安全的,默认的React是不会生成html字符串的。
React.render实例化root组件,启动React框架。如果传入第二个参数(一个DOM节点),它会把生成的标记写入到这个DOM元素里面去。
组合组件
接下来创建其他组件,CommentList和CommentForm,它们同样只是简单的div。
// tutorial2.js
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组件,把上面写的两个组件组合进去。
// tutorial3.js
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList />
<CommentForm />
</div>
);
}
});
从上面代码不难看出,里面不仅有像h1、div一样的基础组件,也有我们自己定义的CommentList组件,在这里,它们都是React自己的组件。JSX语法会识别它们的,将它们都转化成React.createElement(tagname)形式。这样是为了防止全局命名空间的污染。
组件属性
接下来我们创建第三个组件,Comment。我们希望将作者名字和发表的内容传入这个组件,以便我们可以重用代码。首先,我们添加一些内容到CommentList里面去。
// tutorial4.js
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>
);
}
});
注意,这里我们传入了一些数据给Comment组件。从父组件传给子组件的数据被称为props,是属性的英文单词的简写。
使用props
开始创建Comment组件。使用props我们可以读取来自父组件CommentList的组件。
// tutorial5.js
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
在jsx中,使用大括号包含javascript表达式,就可以将组件或文本直接放到React的组件树中。我们通过this.props表达式可以访问传给这个组件的数据,也可以通过this.props.children来访问传递给这个组件的嵌套元素。
添加markdown语法
Markdown提供一种简单的方式来格式化我们的文本,使其看起来更漂亮。比如在文本前后加星号,可以使文本突出显示。
首先,需要引入一个三方的Showdown库,这个库是可以将Markdown语法的文本转化成原始的html内容。
<!-- index.html -->
<head>
<title>Hello React</title>
<script src="http://fb.me/react-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
<script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/showdown/0.3.1/showdown.min.js"></script>
</head>
注意上面上面最后一个script标签中的内容
然后我们把普通的文件转化成Markdown,然后输出:
// tutorial6.js
var converter = new Showdown.converter();
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{converter.makeHtml(this.props.children.toString())}
</div>
);
}
});
上面代码中调用了Showdown三方库做转化。但其实这里还是会有问题的。直接打开页面你会发现,三方库并没有转化html代码。我们看到的仍然是“<p>This is <em>another</em> comment</p>
”。
这里是因为有React的保护机制存在,为了预防xss攻击的。下面介绍一种方式能够显示markdown语法,不过还是建议不要这么做。
// tutorial7.js
var converter = new Showdown.converter();
var Comment = React.createClass({
render: function() {
var rawMarkup = converter.makeHtml(this.props.children.toString());
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
这个特殊的api是故意使插入原始html片段变得复杂的(安全考虑嘛),但对于Showdown,我们需要利用这个后门。
注意,如果要使用这个特性,你必须依赖三方库如Showdown来输出内容,以确保安全。否则。。。。
数据模型
到目前为止,上面的数据都是直接插入的,实际项目中肯定不是这么玩的。下面,我们把一个JSON对象插入到CommentList中去。当然,这个JSON对象实际应该是从服务器读取的,这里,我们先将它写到源码中。
// tutorial8.js
var data = [
{author: "Pete Hunt", text: "This is one comment"},
{author: "Jordan Walke", text: "This is *another* comment"}
];
我们需要使用一个模块化的方式将数据传入到CommentList里面去。这里,我们修改CommentBox组件和React.render方法,通过props的方式将数据传入到CommentList中去。
// tutorial9.js
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')
);
现在,在CommentList中,这个数据是可用的了。我们可以动态的渲染评论了。
// 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>
);
}
});
从服务器获取数据
在这里,我们将不再使用硬编码的方式将数据写在代码中,而是从服务器去取数据。我们需要去掉data属性,然后配置一个url属性,数据将从这个url里面去获取。
// tutorial11.js
React.render(
<CommentBox url="comments.json" />,
document.getElementById('content')
);
这个组件和开始的组件已经不一样,它可以自己渲染。如果这个组件需要获取一些新的评论,那它可能没有可用的数据,除非服务器返回数据。
交互状态
到目前为止,每一个组件都能够使用自己的属性来渲染了。props是不变的,数据来源于它们的父组件。为了实现交互,我们需要使用到可变的state。this.state是组件私有的,可以通过this.setState来改变它的值。当状态更新的时候,这个组件会重新渲染。
render()方法是写给this.props和this.state的方法,状态或者属性的变化总是会触发render被调用。通过这个方式,框架使得ui和输入总是保持一致。
当服务器返回数据,组件这边会根据返回的数据来更新展示。这里,我们添加一些评论的数据给CommentBox组件,这些数据就是组件的状态。
// tutorial12.js
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()在组件的生命周期里面只会执行一次,在执行的时候,它负责设置组件的初始状态。
更新状态
当一个组件第一次被创建,我们希望通过GET的方式从服务器拿到一些JSON数据并且更新状态,以展示最新数据。在一个真实的应用中,这是一次获取数据的完整的过程。但是在这个例子中,我们使用静态的JSON文件来做。
// tutorial13.json
[
{"author": "Pete Hunt", "text": "This is one comment"},
{"author": "Jordan Walke", "text": "This is *another* comment"}
]
我们使用JQuery来帮助我们向服务器发起一个异步的请求
现在,这已经演变成了一个ajax的应用,你需要使用一个web服务器来开发你的应用,而不是使用本地文件。你可以在这里找到基于服务器的例子
// tutorial13.js
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>
);
}
});
上面,当组件被渲染的时候,一个叫做commentDidMount的组件被调用,更新数据的关键是调用了this.setstate,我们使用从服务器获取来的数据更新了状态,这时,UI组件自己就会更新。由于有这样的能力,我们只做了一些小小的改动就实现了实时更新。在这里只是展示了简单的拉取数据的方法,你也可以很简单的使用websockets或其他技术实现。
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')
);
这里我们仅把AJAX调用分离了开,然后在组件初始化的时候和每隔2s去调用它。你可以在你的浏览器中运行代码,并修改comment.json文件;2s内,评论就会更新。
增加新的评论
现在开始创建表单。我们的CommentForm组件应该获取到用户的名称和评论的内容,然后发送请求到服务器上面,最后保存评论。
// tutorial15.js
var CommentForm = React.createClass({
render: function() {
return (
<form className="commentForm">
<input type="text" placeholder="Your name" />
<input type="text" placeholder="Say something..." />
<input type="submit" value="Post" />
</form>
);
}
});
接下来我们可以让表单能够交互。当用户提交表单,我们清空它,然后提交一个请求到服务器,然后刷新评论列表。首先,我们需要监听表单的提交并清空它。
// tutorial16.js
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 = '';
},
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>
);
}
});
Events(事件)
React通过使用驼峰命名的方式给组件添加事件处理函数。我们给表单添加onSubmit函数,当表单提交了有效的数据后,就清空表单中的数据。
在事件中调用preventDefault方法来组件浏览器的默认行为。
Refs(引用)
这里我们使用ref属性,来给孩子组件分配一个名字,然后通过this.refs可以拿到这个组件的引用。可以在组件里使用getDomNode方法来获取到本地浏览器上的DOM元素。
Callbacks as props(属性的回调)
当一个用户提交了评论后,我们需要刷新评论列表。而当CommentBox拿到了最新状态的数据后,这个组件会帮你把剩下的事情都搞定的。so easy。
有时候,我们需要从子组件中传递数据到父组件。为了实现这个功能,我们可以再父组件的render函数中加一个回调(handleCommentSubmint),然后将这个回调传给孩子组件,孩子组件就将这个回调绑定在onCommentSubmint事件上,只要这个事件被触发,回调就会被执行。
// 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>
);
}
});
当用户提交了表单时,我们就调用这个回调
// tutorial18.js
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;
}
this.props.onCommentSubmit({author: author, text: text});
this.refs.author.getDOMNode().value = '';
this.refs.text.getDOMNode().value = '';
},
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>
);
}
});
现在,回调也已经有了,剩下的就是提交到服务器然后再刷新列表了。
// tutorial19.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) {
$.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)
});
},
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>
);
}
});
优化更新
现在我们的应用该有的都已经有了,但是要服务器返回请求后,评论才会出现在列表中,这个过程感觉有些慢。我们可以直接将评论添加到列表中,这样就跑的更快了。
/ tutorial20.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) {
var comments = this.state.data;
var newComments = comments.concat([comment]);
this.setState({data: newComments});
$.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)
});
},
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>
);
}
});