React问答小demo
在学习react初期,看了一些视频和资料,react基础知识差不多学完,跟着网上的一个教程,做了一个小型的问答demo。
需求看图说:
1.点击“添加”按钮,显示问题输入表单,再次点击,隐藏表单。同时,点击“取消”按钮,隐藏表单。
2.输入问题标题和内容后,点击“确认”按钮,将问题显示在下方(按照投票数从高到低)。
3.每个问题有加票和减票功能,在点击的同时,将问题按照投票数从高到低排序。
实现过程:
一、开发环境和工具
1.npm init (生成package.json文件) (按照向导填写各个字段,最后生成 package.json 文件。
容易出错的是: name的值不要和包包同名 。
比如我们后续需要使用npm安装几个包包:browserify react reactify ...则name值如果写作“browserify”或“react”,此依赖会安装失败!
提示如下:
npm WARN install Refusing to install react as a dependency of itself)
2.npm install react --save npm install react-dom --save (最开始就是没有安装react-dom,所以一直渲染不出来)
3.npm install -g gulp
4.npm install --save-dev gulp gulp-browserify gulp-concat gulp-react gulp-connect lodash reactify
(这里注意一下,npm install --save 与 npm install --save-dev 的区别,一个放在package.json 的dependencies , 一个放在devDependencies里面,产品模式用dependencies,开发模式用devDep。)
5.bower init (生成bower.json文件)
6.bower install bootstrap --save
7.新建app文件夹,再在下面建一个js文件夹,创建main.js
8.新建dist文件(压缩后的文件放的地方)
9.创建gulpfile.js
二、代码开发
gulpfile.js内容如下:
1 var gulp = require('gulp'), 2 connect = require('gulp-connect'), 3 browserify = require('gulp-browserify'), 4 concat = require('gulp-concat'), 5 port = process.env.port || 5000; 6 7 gulp.task('browserify',function(){ 8 gulp.src('./app/js/main.js') 9 .pipe(browserify({ 10 transform: 'reactify', 11 })) 12 .pipe(gulp.dest('./dist/js')) 13 }); 14 15 // live reload 16 gulp.task('connect',function(){ 17 connect.server({ 18 // root:'./', 19 port: port, 20 livereload: true 21 }) 22 }) 23 24 // reload Js 25 gulp.task('js',function(){ 26 gulp.src('./dist/**/*.js') 27 .pipe( connect.reload() ) 28 }) 29 30 gulp.task('html',function(){ 31 gulp.src('./app/**/*.html') 32 .pipe( connect.reload() ) 33 }); 34 35 gulp.task('watch',function(){ 36 gulp.watch('./dist/**/*.js',['js']); 37 gulp.watch('./app/**/*.html',['html']); 38 gulp.watch('./app/js/**/*.js',['browserify']); 39 }) 40 41 gulp.task('default',['browserify']); 42 43 gulp.task('serve',['browserify','connect','watch']);
静态页面html:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf8"> 5 <title>React问答 app </title> 6 <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"> 7 <style> 8 .container{ 9 max-width: 800px; 10 } 11 .jumbotron .container{ 12 position: relative; 13 max-width: 800px; 14 } 15 #add-question-btn{ 16 position: absolute; 17 bottom: -20px; 18 right: 20px; 19 } 20 form[name="addQuestion"] .btn{ 21 margin: 20px 0 0 15px; 22 } 23 .media-left{ 24 text-align: center; 25 width:70px; 26 float: left; 27 } 28 .media-left .btn{ 29 margin-bottom: 10px; 30 } 31 .vote-count{ 32 display: block; 33 } 34 </style> 35 </head> 36 <body> 37 <div id="app"> 38 <div class="jumbotron text-center"> 39 <div class="container"> 40 <h1>React问答</h1> 41 <button id="add-question-btn" class="btn btn-success">添加问题</button> 42 </div> 43 </div> 44 <div class="main container"> 45 <form name="addQuestion" class="clearfix"> 46 <div class="form-group"> 47 <label for="qtitle">问题</label> 48 <input type="text" class="form-control" id="qtitle" placeholder="您的问题的标题"> 49 </div> 50 <textarea class="form-control" rows="3" placeholder="问题的描述"></textarea> 51 <button class="btn btn-success pull-right">确认</button> 52 <button class="btn btn-default pull-right">取消</button> 53 </form> 54 <div id="questions" class=""> 55 <div class="media"> 56 <div class="media-left"> 57 <button class="btn btn-default"> 58 <span class="glyphicon glyphicon-chevron-up"></span> 59 <span class="vote-count">22</span> 60 </button> 61 <button class="btn btn-default"> 62 <span class="glyphicon glyphicon-chevron-down"></span> 63 </button> 64 </div> 65 <div class="media-body"> 66 <h4 class="media-heading">产品经理与程序员矛盾的本质是什么?</h4> 67 <p>理性探讨,请勿撕逼。产品经理的主要工作职责是产品设计。接受来自其他部门的需求,经过设计后交付研发。但这里有好些职责不清楚的地方。</p> 68 </div> 69 </div> 70 71 <div class="media"> 72 <div class="media-left"> 73 <button class="btn btn-default"> 74 <span class="glyphicon glyphicon-chevron-up"></span> 75 <span class="vote-count">12</span> 76 </button> 77 <button class="btn btn-default"> 78 <span class="glyphicon glyphicon-chevron-down"></span> 79 </button> 80 </div> 81 <div class="media-body"> 82 <h4 class="media-heading">热爱编程是一种怎样的体验?</h4> 83 <p>别人对玩游戏感兴趣,我对写代码、看技术文章感兴趣;把泡github、stackoverflow、v2ex、reddit、csdn当做是兴趣爱好;遇到重复的工作,总想着能不能通过程序实现自动化;喝酒的时候把写代码当下酒菜,边喝边想边敲;不给工资我也会来加班;做梦都在写代码。</p> 84 </div> 85 </div> 86 87 </div> 88 89 </div> 90 </div> 91 92 <!-- 93 <script src="/dist/js/main.js"></script> --> 94 </body> 95 </html>
接下来的react代码转化,我就不详细谢了,在这里贴出我的项目文件展示:
index.html
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf8"> 5 <title>React问答 app </title> 6 <link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css"> 7 <style> 8 .container{ 9 max-width: 800px; 10 } 11 .jumbotron .container{ 12 position: relative; 13 max-width: 800px; 14 } 15 #add-question-btn{ 16 position: absolute; 17 bottom: -20px; 18 right: 20px; 19 } 20 form[name="addQuestion"] .btn{ 21 margin: 20px 0 0 15px; 22 } 23 .media-left{ 24 text-align: center; 25 width:70px; 26 float: left; 27 } 28 .media-left .btn{ 29 margin-bottom: 10px; 30 } 31 .vote-count{ 32 display: block; 33 } 34 </style> 35 </head> 36 <body> 37 <div id="app"> </div> 38 39 <script src="/dist/js/main.js"></script> 40 41 </body> 42 </html>
main.js
1 var React = require('react'); 2 var ReactDOM = require('react-dom'); 3 var QuestionApp = require('./components/QuestionApp.js'); 4 /* 模块名可使用相对路径(以./开头),或者是绝对路径(以/或C:之类的盘符开头)*/ 5 6 var mainCom = ReactDOM.render( 7 <QuestionApp />, 8 document.getElementById('app') 9 );
QuestionApp.js
1 var React = require('react'); 2 var ShowAddBtn = require('./ShowAddBtn.js'); 3 var QuestionForm = require('./QuestionForm.js'); 4 var QuestionList = require('./QuestionList.js'); 5 module.exports = React.createClass({ 6 getInitialState: function(){ 7 var questions = [ 8 { 9 key: 1, 10 title: '产品经理与程序员矛盾的本质是什么?', 11 description: '理性探讨,请勿撕逼。产品经理的主要工作职责是产品设计。接受来自其他部门的需求,经过设计后交付研发。但这里有好些职责不清楚的地方。', 12 voteCount: 2 13 }, 14 { 15 key: 2, 16 title: '热爱编程是一种怎样的体验?', 17 description: '别人对玩游戏感兴趣,我对写代码、看技术文章感兴趣;把泡github、stackoverflow、v2ex、reddit、csdn当做是兴趣爱好;遇到重复的工作,总想着能不能通过程序实现自动化;喝酒的时候把写代码当下酒菜,边喝边想边敲;不给工资我也会来加班;做梦都在写代码。', 18 voteCount: 3 19 } 20 ]; 21 questions = this.questionSort(questions); //sort the init questions 22 return { 23 displayForm: false, 24 questions: questions 25 }; 26 }, 27 onToggleForm: function(){ 28 this.setState({ 29 displayForm: !this.state.displayForm 30 }); 31 }, 32 onQuestionNew: function(newQuestion){ 33 newQuestion.key = this.state.questions.length + 1; 34 var newQuestions = this.state.questions.concat(newQuestion); 35 newQuestions = this.questionSort(newQuestions); 36 this.setState({ 37 questions: newQuestions 38 }); 39 }, 40 questionSort: function(questions){ 41 questions.sort(function(a,b){ 42 return b.voteCount - a.voteCount; 43 }); 44 return questions;//之前一直报错,这里忘记return 45 }, 46 onVote: function(nowKey, newVoteCount){ 47 var questions = this.state.questions; 48 var index = 0; 49 for(var i=0; i<questions.length; i++){ 50 if(questions[i].key == nowKey){ 51 index = i; 52 } 53 } 54 questions[index].voteCount = newVoteCount; 55 questions = this.questionSort(questions); 56 this.setState({ 57 questions: questions 58 }); 59 }, 60 render: function(){ 61 return ( 62 <div> 63 <div className="jumbotron text-center"> 64 <div className="container"> 65 <h1>React问答</h1> 66 <ShowAddBtn onToggleForm={this.onToggleForm}/> 67 </div> 68 </div> 69 <div className="main container"> 70 <QuestionForm onQuestionNew={this.onQuestionNew} displayForm={this.state.displayForm} onToggleForm={this.onToggleForm}/> 71 <QuestionList onVote={this.onVote} questions={this.state.questions}/> 72 </div> 73 </div> 74 ) 75 } 76 });
ShowAddBtn.js
1 var React = require('react'); 2 module.exports = React.createClass({ 3 render: function(){ 4 return ( 5 <button id="add-question-btn" className="btn btn-success" onClick={this.props.onToggleForm}>添加问题</button> 6 ) 7 } 8 });
QuestionForm.js
1 var React = require('react'); 2 module.exports = React.createClass({ 3 handleSubmit: function(e){ 4 e.preventDefault(); 5 var newQuestion = { 6 title: this.refs.title.value, 7 description: this.refs.description.value, 8 voteCount: 0 9 }; 10 this.refs.questionForm.reset(); 11 this.props.onQuestionNew(newQuestion); 12 }, 13 render: function(){ 14 return ( 15 <form name="addQuestion" className="clearfix" ref="questionForm" 16 style={{display: this.props.displayForm ? 'block' : 'none'}} 17 onSubmit={this.handleSubmit}> 18 <div className="form-group"> 19 <label htmlFor="qtitle">问题</label> 20 <input ref="title" type="text" className="form-control" id="qtitle" placeholder="您的问题的标题"/> 21 </div> 22 <textarea ref="description" className="form-control" rows="3" placeholder="问题的描述"></textarea> 23 <button className="btn btn-success pull-right">确认</button> 24 <a className="btn btn-default pull-right" onClick={this.props.onToggleForm}>取消</a> 25 </form> 26 ) 27 } 28 });
QuestionList.js
1 var React = require('react'); 2 var QuestionItem = require('./QuestionItem.js'); 3 module.exports = React.createClass({ 4 render: function(){ 5 var questions = this.props.questions; 6 var _this = this;//这里的this要单独保存,否则下面的map中的this指的是循环的每个对象 7 var questionComps = questions.map(function(qst){ 8 return <QuestionItem 9 key={qst.key} 10 questionKey={qst.key} 11 title={qst.title} 12 description={qst.description} 13 voteCount={qst.voteCount} 14 onVote={_this.props.onVote}/> 15 }); 16 //开始一直报错,是因为这里,render里面,return(), 没有写最外层div 17 return ( 18 <div id="questions" className=""> 19 {questionComps}/*直接放个数组在这里,他会自动去循环*/ 20 </div> 21 ) 22 } 23 });
QuestionItem.js
1 var React = require('react'); 2 module.exports = React.createClass({ 3 voteUp: function(){ 4 var newVoteCount = parseInt(this.props.voteCount, 10) + 1; 5 //this.props.questionKey这里必须重 6 // 新定义一个questionKey属性, 不能this.props.key 7 this.props.onVote(this.props.questionKey, newVoteCount); 8 }, 9 voteDown: function(){ 10 var newVoteCount = parseInt(this.props.voteCount, 10) - 1; 11 this.props.onVote(this.props.questionKey, newVoteCount); 12 }, 13 render: function(){ 14 return ( 15 <div className="media"> 16 <div className="media-left"> 17 <button className="btn btn-default" onClick={this.voteUp}> 18 <span className="glyphicon glyphicon-chevron-up"></span> 19 <span className="vote-count">{this.props.voteCount}</span> 20 </button> 21 <button className="btn btn-default" onClick={this.voteDown}> 22 <span className="glyphicon glyphicon-chevron-down"></span> 23 </button> 24 </div> 25 <div className="media-body"> 26 <h4 className="media-heading">{this.props.title}</h4> 27 <p>{this.props.description}</p> 28 </div> 29 </div> 30 ) 31 } 32 33 });
到这里,所有代码就完成了,在命令行里面执行gulp serve命令,然后在浏览器中访问localhost:5000。
到此整个小demo就算完成了。对于react-router, react redux等深入知识点还在进一步学习过程中,欢迎大家提出问题,一起讨论。