├── .gitignore ├── README.md ├── app.js ├── bin └── www ├── package.json ├── public ├── bundle.js ├── javascripts │ ├── commentBox.js │ └── index.js └── stylesheets │ └── style.css ├── routes ├── comment.js ├── index.js ├── schema.js └── users.js ├── views └── index.html └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/data/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React官网发表评论实例重写 加入后台及绑定数据库 2 | 3 | 后台使用express,并把数据存入mongodb数据库,前端加入webpack编译,react语法完全用ES6重写,并利用setState更新状态来取消官网极其影响性能的轮询。 4 | 5 | ##使用方式 6 | 7 | clone后运行npm install(推荐使用淘宝镜像cnpm,会快很多),再开启mongodb,然后npm start 8 | 然后访问[http://localhost:8888]我们就可以看到项目跑起来了。 9 | 10 | ##功能 11 | 12 | 一个简单的发表评论的功能,可以删除评论。发表和删除都做到实时刷新。 13 | 14 | 15 | ##一点感想 16 | 17 | React的这个实例看似简单,但是我在重写的时候确是踩了不少的坑。因为一开始对于React部分的代码理解不够深入,我在用express搭建后台和试图用状态更新替代原本的每两秒轮询一次的时候,都遇到了不少问题。随着我在论坛(如stackoverflow)查询相关的问题和文档,问题逐渐解决。当查到论坛上和我相同的问题被解答并解决时,竟然有一种幸福感╮(╯▽╰)╭ 。嗯,问题总是能解决的。 -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | var comment = require('./routes/comment'); 8 | 9 | var app = express(); 10 | 11 | // view engine setup 12 | var engine = require('consolidate'); 13 | app.set('views', path.join(__dirname, 'views')); 14 | app.engine('html', engine.mustache); 15 | app.set('view engine', 'html'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | 26 | // 对所有(/)URL或路由返回index.html 27 | app.get('/', function (req, res) { 28 | res.render('index'); 29 | }); 30 | app.get('/comments', comment.list); 31 | app.get('/comments/:id', comment.get); 32 | app.delete('/comments/:id', comment.delete,comment.list); 33 | app.post('/comments', comment.add,comment.list); 34 | app.put('/comments/:id', comment.update); 35 | 36 | // 启动一个服务,监听从8888端口进入的所有连接请求 37 | var server = app.listen(8888, function(){ 38 | var host = server.address().address; 39 | var port = server.address().port; 40 | console.log('mongod --dbpath C:\Users\12510\Desktop\case\express+webpack\commentbox\public\data Listening at http://%s:%s', host, port); 41 | }); 42 | 43 | module.exports = app; 44 | -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var app = require('../app'); 8 | var debug = require('debug')('commentbox:server'); 9 | var http = require('http'); 10 | 11 | /** 12 | * Get port from environment and store in Express. 13 | */ 14 | 15 | var port = normalizePort(process.env.PORT || '3000'); 16 | app.set('port', port); 17 | 18 | /** 19 | * Create HTTP server. 20 | */ 21 | 22 | var server = http.createServer(app); 23 | 24 | /** 25 | * Listen on provided port, on all network interfaces. 26 | */ 27 | 28 | server.listen(port); 29 | server.on('error', onError); 30 | server.on('listening', onListening); 31 | 32 | /** 33 | * Normalize a port into a number, string, or false. 34 | */ 35 | 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | 56 | function onError(error) { 57 | if (error.syscall !== 'listen') { 58 | throw error; 59 | } 60 | 61 | var bind = typeof port === 'string' 62 | ? 'Pipe ' + port 63 | : 'Port ' + port; 64 | 65 | // handle specific listen errors with friendly messages 66 | switch (error.code) { 67 | case 'EACCES': 68 | console.error(bind + ' requires elevated privileges'); 69 | process.exit(1); 70 | break; 71 | case 'EADDRINUSE': 72 | console.error(bind + ' is already in use'); 73 | process.exit(1); 74 | break; 75 | default: 76 | throw error; 77 | } 78 | } 79 | 80 | /** 81 | * Event listener for HTTP server "listening" event. 82 | */ 83 | 84 | function onListening() { 85 | var addr = server.address(); 86 | var bind = typeof addr === 'string' 87 | ? 'pipe ' + addr 88 | : 'port ' + addr.port; 89 | debug('Listening on ' + bind); 90 | } 91 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commentbox", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www" 7 | }, 8 | "dependencies": { 9 | "body-parser": "~1.15.1", 10 | "consolidate": "^0.14.5", 11 | "cookie-parser": "~1.4.3", 12 | "debug": "~2.2.0", 13 | "express": "~4.13.4", 14 | "jade": "~1.11.0", 15 | "morgan": "~1.7.0", 16 | "mustache": "^2.3.0", 17 | "serve-favicon": "~2.3.0" 18 | }, 19 | "devDependencies": { 20 | "babel-core": "^6.18.2", 21 | "babel-loader": "^6.2.8", 22 | "babel-preset-es2015": "^6.18.0", 23 | "babel-preset-react": "^6.16.0", 24 | "consolidate": "^0.14.5", 25 | "jquery": "^3.1.1", 26 | "jsx-loader": "^0.13.2", 27 | "marked": "^0.3.6", 28 | "mongoose": "^4.7.1", 29 | "mustache": "^2.3.0", 30 | "react": "^15.4.1", 31 | "react-dom": "^15.4.1", 32 | "webpack": "^1.13.3", 33 | "webpack-dev-middleware": "^1.8.4" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /public/javascripts/commentBox.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import marked from 'marked'; 4 | import $ from 'jquery'; 5 | 6 | class Comment extends React.Component { 7 | onclick (index){this.props.click(this.props.index)} 8 | rawMarkup () { 9 | let rawMarkup = marked(this.props.children.toString(), {sanitize: true}); 10 | return { __html: rawMarkup }; 11 | } 12 | render () { 13 | return ( 14 |
15 |

16 | {this.props.author} 17 |

18 | 19 | 20 |
21 | ); 22 | } 23 | }; 24 | class CommentList extends React.Component { 25 | clicks(index){this.props.delete(index)} 26 | render () { 27 | return ( 28 |
29 | {this.props.data.map((comment)=>{ 30 | return ( 31 | 32 | {comment.text} 33 | 34 | ); 35 | })} 36 |
37 | ); 38 | } 39 | }; 40 | 41 | class CommentForm extends React.Component { 42 | handleSubmit (e) { 43 | e.preventDefault(); 44 | var author = this.refs.author.value.trim(); 45 | var text = this.refs.text.value.trim(); 46 | if (!text || !author) { 47 | return; 48 | } 49 | this.props.onCommentSubmit({author: author, text: text}); 50 | this.refs.author.value = ''; 51 | this.refs.text.value = ''; 52 | return; 53 | } 54 | render () { 55 | return ( 56 |
57 | 58 | 59 | 60 |
61 | ); 62 | } 63 | }; 64 | 65 | class CommentBox extends React.Component { 66 | constructor(props){ 67 | super(props); 68 | this.state={ 69 | data:[] 70 | } 71 | } 72 | deleteClick (index) { 73 | var url = '/comments/'+index; 74 | $.ajax({ 75 | url: url, 76 | type: 'delete', 77 | success : (data) => { 78 | this.setState({data: data}); 79 | } 80 | }) 81 | } 82 | loadCommentsFromServer () { 83 | $.ajax({ 84 | url: this.props.url, 85 | dataType: 'json', 86 | cache: false, 87 | success : (data) => { 88 | this.setState({data: data}); 89 | }, 90 | error (xhr, status, err) { 91 | console.error(this.props.url, status, err.toString()); 92 | } 93 | }); 94 | } 95 | handleCommentSubmit (comment) { 96 | var comments = this.state.data; 97 | var newComments = comments.concat([comment]); 98 | this.setState({data: newComments}); 99 | $.ajax({ 100 | url: this.props.url, 101 | dataType: 'json', 102 | type: 'POST', 103 | data: comment, 104 | success : (data) => { 105 | this.setState({data: data}); 106 | }, 107 | error (xhr, status, err) { 108 | console.error(this.props.url, status, err.toString()); 109 | } 110 | }); 111 | } 112 | componentDidMount () { 113 | this.loadCommentsFromServer(); 114 | //setInterval(this.loadCommentsFromServer, this.props.pollInterval); 115 | } 116 | render () { 117 | return ( 118 |
119 |

我的留言板

120 | 121 | 122 |
123 | ); 124 | } 125 | }; 126 | 127 | module.exports = CommentBox; -------------------------------------------------------------------------------- /public/javascripts/index.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDOM = require('react-dom'); 3 | var CommentBox = require('./commentBox'); 4 | 5 | ReactDOM.render( 6 | , 7 | document.getElementById('commentbox') 8 | ); -------------------------------------------------------------------------------- /public/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px; 3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; 4 | } 5 | 6 | a { 7 | color: #00B7FF; 8 | } 9 | -------------------------------------------------------------------------------- /routes/comment.js: -------------------------------------------------------------------------------- 1 | //var comments = require('../public/data/comment').comments; 2 | var mongoose = require('mongoose'); 3 | mongoose.connect('mongodb://localhost:27017/comments') 4 | var Comment = require('./schema.js'); 5 | 6 | exports.list = function(req, res){ 7 | Comment.find(function(err, comment) { 8 | res.json(comment); 9 | }) 10 | }; 11 | 12 | exports.get = function(req, res){ 13 | if(comments.length <= req.params.id || req.params.id < 0) { 14 | res.statusCode = 404; 15 | return res.send('Error 404: No comment found'); 16 | } 17 | var q = comments[req.params.id]; 18 | res.json(q); 19 | }; 20 | 21 | 22 | exports.delete = function(req, res, next){ 23 | console.log(req.params.id); 24 | Comment.remove({_id: req.params.id}, function(err,status){ 25 | if(err){console.log(err)}; 26 | next(); 27 | }) 28 | }; 29 | 30 | 31 | exports.update = function(req, res){ 32 | res.setHeader('Content-Type', 'application/json;charset=utf-8'); 33 | for(var i=0;i 2 | 3 | 4 | 5 | 留言板 6 | 7 | 8 |
9 | 10 | 11 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: [ 3 | './public/javascripts/index.js' 4 | ], 5 | output: { 6 | path: __dirname + '/public/', 7 | publicPath: "/public/", 8 | filename: 'bundle.js' 9 | }, 10 | module: { 11 | loaders: [ 12 | { test: /\.js|\.jsx|\.es6$/, exclude: /node_modules/, loader: "babel?presets[]=react,presets[]=es2015"} 13 | ] 14 | } 15 | }; --------------------------------------------------------------------------------