React是Facebook公司推出的前端组件化解决方案,目的在于解决前端开发中存在的各个痛点。目前,前端框架与库层出不穷,形成了异常繁荣的局面,那么Facebook为何还要重复造轮子呢?究其原因,Facebook认为现有的前端解决方案都不是很好(甚至Facebook认为MVC本身也是有问题的),无法解决自己在实际开发中面临的种种问题,于是自己就开发出了React并将其开源;同时,基于React,Facebook又推出了React Native,旨在使用前端开发者熟悉的JavaScript等技术来开发原生App,实现一套代码运行在iOS与Android等移动平台上。一经推出,React与React Native就得到了开发者的极大关注,短时间内其在GitHub上就获得了大量的关注,目前也是前端开发领域最火热的技术之一。基于这一点,本文将会介绍React开发入门知识,通过一个实际可运行的案例带领大家一步步掌握React开发的步骤,厘清React开发的各项知识点,同时对于开发过程中所用的工具有一定的认识和掌握。
本文将会重点关注于React,通过一个实际可运行的示例来一步步演示React的开发过程,同时还会给出关于工具、开发等的一些最佳实践。
本文所选取的示例 来自于React官网,不过进行了一定程度的增强和完善,更加便于React新手学习;同时,对于工具的使用也给出了一些建议。
虽然本文介绍的是React前端开发,不过为了保持示例的完整性,文中同时给出了后端代码,这样学习者就可以直接在本机启动服务器运行示例了。该示例虽然不大,但使用的工具还是不少的,希望大家能一步步跟着我的步伐操练起来。
本文所使用的主要工具与库如下所示:
首先需要安装项目所用的工具,该项目的后端采用Node进行开发,因此需要先安装Node。
安装nvm:
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.3/install.sh | bash
只需通过上述一行命令即可在Mac上安装nvm。
安装完毕后在Terminal中输入命令:nvm help即可列出nvm支持的各项命令,比如说:
nvm install v6.3.1
上述命令同时还会自动安装v6.3.1版本的Node所对应的npm,安装完毕后输入命令:
nvm alias default v6.3.1
{
"name": "React_Tutorial",
"version": "0.1.1",
"private": true,
"main": "server.js",
"dependencies": {
"body-parser": "^1.4.3",
"express": "^4.4.5",
"uuid": "^2.0.0"
}
}
我们这个项目使用到了Express框架、body-parser以及用于生成uuid的uuid库。
然后在项目所在目录下执行命令:
npm install
在项目目录下新建目录public,然后在public目录下新建两个子目录:css与scripts,分别用于存放项目所用的CSS文件与JavaScript文件。
在项目根目录下新建文件server.js,在server.js文件中编写如下代码:
var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var uuid = require('uuid');
var app = express();
var COMMENTS_FILE = path.join(__dirname, 'comments.json');
app.set('port', (process.env.PORT || 3000));
app.use('/', express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(function(req, res, next) {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Cache-Control', 'no-cache');
next();
});
app.get('/api/comments', function(req, res) {
fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) {
console.error(err);
process.exit(1);
}
res.json(JSON.parse(data));
});
});
app.post('/api/comments', function(req, res) {
fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) {
console.error(err);
process.exit(1);
}
var comments = JSON.parse(data);
var newComment = {
id: uuid.v4(),
author: req.body.author,
text: req.body.text,
};
comments.push(newComment);
fs.writeFile(COMMENTS_FILE, JSON.stringify(comments, null, 4), function(err) {
if (err) {
console.error(err);
process.exit(1);
}
res.json(comments);
});
});
});
app.listen(app.get('port'), function() {
console.log('Server started: http://localhost:' + app.get('port') + '/');
});
该文件主要有两个作用:
该文件是一个典型的Nodejs服务器文件,使用到了目前Nodejs领域流行的Express框架(Koa是另外一个流行的的服务器框架,是由Express框架的原班人马开发的,感兴趣的读者也可以了解一下);此外,读者可以看到,该文件还向外提供了一个接口/api/comments,同时提供了两种调用方式,分别是get方式与post方式,这实际上是一个典型的RESTFul接口,针对评论这一资源提供两种调用方式:get用于查询评论,post则用于发表评论。同时,应用为了简化,将新的评论保存到了comments.json文件中。
另外值得一提的是,对于每一个评论都会有一个唯一的主键,这里的主键生成方式采用了uuid模块的方法,用于生成全局唯一的uuid标识符作为每一条新评论的主键。
通过如下命令来启动node server:
node server
服务器启动后即会监听3000端口的访问。
确保服务器启动没有任何异常信息后,使用ctrl+c来关闭服务器。
在public目录下的css目录中新建一个CSS文件base.css,其内容如下所示:
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: 0 5px;
}
h1, h2, h3, h4 {
font-weight: bold;
margin: 0 0 15px;
padding: 0;
}
h1 {
border-bottom: 1px solid #ddd;
font-size: 2.5em;
}
h2 {
border-bottom: 1px solid #eee;
font-size: 2em;
}
h3 {
font-size: 1.5em;
}
h4 {
border-bottom: 1px solid #eee;
font-size: 1.2em;
}
p, ul {
margin: 15px 0;
}
ul {
padding-left: 30px;
}
该CSS文件的内容都是一些基本的样式信息,这里不再赘述。
下面进入到本文最为关键与核心的部分——React。
在public目录中新建文件index.html,输入如下内容:
React Tutorial
从中可以看到,该文件基本上算是一个空的html文件,只是引入了一些外部js文件与css文件,这正是React编程的方式。
除了引入方才创建的base.css文件外,该文件在head部分还引入了5个js文件,下面分别介绍:
这里值得重点关注的是前3个文件,react.js与react-dom.js是我们使用React所必须的两个文件;另外,由于React建议使用JSX语法来编写组件声明,而JSX需要在浏览器端转换为原生的JavaScript文件,因此需要一个转换工具,而browser.js文件就是起到这个作用的;jquery.min.js与remarkable.min.js则是针对于本项目所需的功能而引入的两个文件。
下面来编写本项目所需的最后一个文件。在public目录的scripts目录下新建文件test.js。
React是基于组件化开发的,因此在一开始我们需要先设计好页面的组件以及组件之间的关系。下面是页面运行时的截图:
从图中可以看到,该页面实际上由几个部分构成:
综上所述,该页面的组件构成与包含关系应该如下图所示:
接下来就需要定义各个组件了,test.js文件如下代码清单所示:
var Comment = React.createClass({
rawMarkup: function() {
var md = new Remarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
},
render: function() {
return (
{this.props.author} 说:
);
}
});
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
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) {
this.setState({data: comments});
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 (
Comments
);
}
});
var CommentList = React.createClass({
render: function() {
var commentNodes = this.props.data.map(function(comment) {
return (
{comment.text}
);
});
return (
{commentNodes}
);
}
});
var CommentForm = React.createClass({
getInitialState: function() {
return {author: '', text: ''};
},
handleAuthorChange: function(e) {
this.setState({author: e.target.value});
},
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
handleSubmit: function(e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
this.props.onCommentSubmit({author: author, text: text});
this.setState({author: '', text: ''});
},
render: function() {
return (
);
}
});
ReactDOM.render(
,
document.getElementById('contentContainer')
);
从上面的代码中我们可以看到,系统一共定义了4个组件,分别是Comment、CommentBox、CommentList与CommentForm,最下面则通过ReactDOM的render方法将CommentBox组件插入到外层容器contentContainer中。
在上述代码中,我们与服务器之间的异步通信使用了jQuery,实际上也可以使用其他方式,React对于这一点并没有任何限制。而组件之间的包含关系则是CommentList包含了Comment、CommentBox包含了CommentList与CommentForm。最后则通过ReactDOM的render方法将CommentBox插入到了外层容器中。
上述代码中定义组件的方式使用了React.createClass方法,这是React提供的定义组件的一般方法,每一个组件都需要提供一个render方法,用于指定组件的渲染方式与包含关系,这里使用了React 的JSX语法。实际上,也可以通过原生的JavaScript来实现,不过React官方强烈推荐使用JSX语法,因为它简洁、可读性好,同时类似于XML语法,使用起来非常直观方便,感兴趣的读者可以到React官网阅读JSX语法指南,还是比较简单的。
另外,在ReactDOM的render方法中,我们为CommentBox组件指定了属性pollInterval,值为3000,这表示每隔3秒钟会向服务器发起一个异步请求,用于获取最新的评论列表。实际上,这里可以通过WebSocket来实现,效率更好,同时也省去了轮询的烦恼,这一步可以由读者自行实现。
数据的存储我们使用comments.json文件,由于本教程主要讲解React的使用,因此存储这块就没有使用数据库,实际情况下,这部分应该使用诸如MongoDB之类的数据库来实现,也是比较容易的。如果使用MongoDB,那么可以使用Mongoose,这是个面向Nodejs的MongoDB ODM(Object-Document Mapping,对象文档映射)框架,可以实现领域模型与数据库文档之间的映射,使用起来非常方便。
本文主要起到React入门的作用,目的在于通过一个实际可运行的示例来演示React的基本用法,并未涉及到React的深层次知识,比如说Flux、Redux、WebPack与React整合等等。
学习是需要循序渐进的,只有入门了才能进一步深入下去,希望读者在学习完本文后能够开启React的学习之旅,我也将在后面为大家带来React的深度内容介绍。