React入门指引与实战
导言React是Facebook公司推出的前端组件化解决方案,目的在于解决前端开发中存在的各个痛点。目前,前端框架与库层出不穷,形成了异常繁荣的局面,那么Facebook为何还要重复造轮子呢?究其原因,Facebook认为现有的前端解决方案都不是很好(甚至Facebook认为MVC本身也是有问题的),无法解决自己在实际开发中面临的种种问题,于是自己就开发出了React并将其开源;同时,基于React,Facebook又推出了React Native,旨在使用前端开发者熟悉的JavaScript等技术来开发原生App,实现一套代码运行在iOS与Android等移动平台上。一经推出,React与React Native就得到了开发者的极大关注,短时间内其在GitHub上就获得了大量的关注,目前也是前端开发领域最火热的技术之一。基于这一点,本文将会介绍React开发入门知识,通过一个实际可运行的案例带领大家一步步掌握React开发的步骤,厘清React开发的各项知识点,同时对于开发过程中所用的工具有一定的认识和掌握。 开发IDE前端框架分析
值得一提的是,目前的前端开发领域非常繁荣,各种工具、库、理念层出不穷,再也不是前几年jQuery一个库打天下的境况了。随着前端开发领域的持续发展,这一局面一定还会继续下去,我个人认为目前前端的流行趋势是这样的:
本文将会重点关注于React,通过一个实际可运行的示例来一步步演示React的开发过程,同时还会给出关于工具、开发等的一些最佳实践。 本文所选取的示例 来自于React官网,不过进行了一定程度的增强和完善,更加便于React新手学习;同时,对于工具的使用也给出了一些建议。开发工具虽然本文介绍的是React前端开发,不过为了保持示例的完整性,文中同时给出了后端代码,这样学习者就可以直接在本机启动服务器运行示例了。该示例虽然不大,但使用的工具还是不少的,希望大家能一步步跟着我的步伐操练起来。 本文所使用的主要工具与库如下所示:
项目简介
本文将会带领读者使用React实现一个简单的留言系统,使用者通过输入自己的姓名与评论内容来发布评论。评论发布完毕后可以显示出评论列表;此外,程序还将通过轮询的方式在不刷新页面的情况下自动获取其他评论者的评论内容。这就是本应用的主题功能。由于本文主要突出React的使用介绍,因此对于样式等方面几乎没有做任何优化。该系统实际运行的样子如下图所示:
项目开发首先需要安装项目所用的工具,该项目的后端采用Node进行开发,因此需要先安装Node。
直接安装Node是非常简单的事情,在Mac平台上只需从Node官网(https://nodejs.org)下载Node的安装包即可双击安装,同时还会自动安装npm(Node的包管理器)。目前Node的最新版本是6.3.1。不过这样安装Node存在一个严重的问题:Node现在的发展速度非常快,版本更迭也非常频繁,可能安装不就之后Node就发布了新的版本,这时如果要体验Node的最新版特性就变得比较困难了。因为一方面要保留老的Node版本供系统开发所用,另一方面还想要尝试Node的新特性。那该怎么办呢?答案就是使用nvm(Node Version Manager)。
nvm是一个优秀的Node版本管理器,可以使多个Node版本在同一台电脑上共存并且互不影响,而且还能轻松实现各个版本的Node切换。此外,它还支持查询本地安装的各个Node版本,支持查询远程发布的所有Node版本与iojs(之前从Node分裂出来的一个版本,不过后来Node与iojs又合并了)版本,并且安装也是非常便捷的。
安装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 上述命令用于将v6.3.1这一版本作为系统默认使用的Node版本。至此为止,Node与npm就安装完毕了。
在工程中新建文件package.json(Node的包管理描述文件),输入项目所需用到的依赖以及项目名字等基本信息,如下代码所示:
{ "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 这时,npm会根据package.json的文件内容自动解析依赖并下载到项目目录的node_modules下面,如下图所示: 在项目目录下新建目录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',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,输入如下内容: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>React Tutorial</title> <!-- Not present in the tutorial. Just for basic styling. --> <link rel="stylesheet" href="css/base.css" /> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react-dom.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.6.16/browser.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/remarkable/1.6.2/remarkable.min.js"></script> </head> <body> <div id="contentContainer"></div> <script type="text/babel" src="scripts/test.js"></script> </body> </html> 从中可以看到,该文件基本上算是一个空的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 ( <div className="comment"> <h4 className="commentAuthor"> {this.props.author} 说: <span dangerouslySetInnerHTML={this.rawMarkup()} /> </h4> </div> ); } }); 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,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,type: 'POST',data: comment,err) { this.setState({data: comments}); console.error(this.props.url,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> ); } }); var CommentList = React.createClass({ render: function() { var commentNodes = this.props.data.map(function(comment) { return ( <Comment author={comment.author} key={comment.id}> {comment.text} </Comment> ); }); return ( <div className="commentList"> {commentNodes} </div> ); } }); 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 ( <form className="commentForm" onSubmit={this.handleSubmit}> <input type="text" placeholder="昵称" value={this.state.author} onChange={this.handleAuthorChange} /> <input type="text" placeholder="评论内容" value={this.state.text} onChange={this.handleTextChange} /> <input type="submit" value="提交评论" /> </form> ); } }); ReactDOM.render( <CommentBox url="/api/comments" pollInterval={3000} />,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的深度内容介绍。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |