├── .gitignore ├── app ├── js │ ├── main.js │ ├── components │ │ ├── ShowAddButton.js │ │ ├── QuestionItem.js │ │ └── QuestionForm.js │ └── containers │ │ ├── QuestionList.js │ │ └── QuestionApp.js └── css │ └── index.css ├── bower.json ├── index.html ├── package.json ├── gulpfile.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | coverage 4 | dist 5 | node_modules 6 | bower_components -------------------------------------------------------------------------------- /app/js/main.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var ReactDOM = require('react-dom'); 3 | var QuestionApp = require('./containers/QuestionApp'); 4 | 5 | var mainCom = ReactDOM.render( 6 | , 7 | document.getElementById('app') 8 | ); 9 | -------------------------------------------------------------------------------- /app/js/components/ShowAddButton.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | module.exports = React.createClass({ 4 | render:function(){ 5 | return ( 6 | 7 | ) 8 | } 9 | }); 10 | 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-zhihu", 3 | "description": "", 4 | "main": "index.js", 5 | "authors": [ 6 | "tsrot " 7 | ], 8 | "license": "ISC", 9 | "homepage": "", 10 | "private": true, 11 | "ignore": [ 12 | "**/.*", 13 | "node_modules", 14 | "bower_components", 15 | "test", 16 | "tests" 17 | ], 18 | "dependencies": { 19 | "bootstrap": "^3.3.7" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | React问答 app 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 14 | 15 | -------------------------------------------------------------------------------- /app/css/index.css: -------------------------------------------------------------------------------- 1 | .container{ 2 | max-width: 800px; 3 | } 4 | .jumbotron .container{ 5 | position: relative; 6 | max-width: 800px; 7 | } 8 | #add-question-btn{ 9 | position: absolute; 10 | bottom: -20px; 11 | right: 20px; 12 | } 13 | form[name="addQuestion"] .btn{ 14 | margin: 20px 0 0 15px; 15 | } 16 | .media-left{ 17 | text-align: center; 18 | width:70px; 19 | float: left; 20 | } 21 | .media-left .btn{ 22 | margin-bottom: 10px; 23 | } 24 | .vote-count{ 25 | display: block; 26 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-zhihu", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "react": "^15.3.2", 13 | "react-dom": "^15.3.2" 14 | }, 15 | "devDependencies": { 16 | "gulp": "^3.9.1", 17 | "gulp-browserify": "^0.5.1", 18 | "gulp-concat": "^2.6.0", 19 | "gulp-connect": "^5.0.0", 20 | "gulp-git": "^1.12.0", 21 | "gulp-react": "^3.1.0", 22 | "lodash": "^4.16.6", 23 | "reactify": "^1.1.1" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/js/containers/QuestionList.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var QuestionItem = require('../components/QuestionItem'); 3 | 4 | module.exports = React.createClass({ 5 | // getInitialState:function(){ 6 | // return { 7 | // onVote:this.props.onVote, 8 | // } 9 | // }, 10 | 11 | render:function(){ 12 | var onVote = this.props.onVote; 13 | var questions = this.props.questions; 14 | if(!Array.isArray(questions)) throw new Error('this.props.questions必需是数组!'); 15 | 16 | var questionComps = questions.map(function(q){ 17 | return 26 | }) 27 | 28 | return ( 29 |
30 | {questionComps} 31 |
32 | ) 33 | } 34 | }); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'), 2 | connect = require('gulp-connect'), 3 | browserify = require('gulp-browserify'), 4 | concat = require('gulp-concat'), 5 | 6 | port = process.env.port || 5000 ; 7 | 8 | gulp.task('browserify',function(){ 9 | gulp.src('./app/js/main.js') 10 | .pipe(browserify({ 11 | transform:'reactify', 12 | })) 13 | .pipe(gulp.dest('./dist/js')) 14 | }); 15 | 16 | gulp.task('connect',function(){ 17 | connect.server({ 18 | root:'./', 19 | port:port, 20 | livereload:true, 21 | }) 22 | }); 23 | 24 | gulp.task('js',function(){ 25 | gulp.src('./dist/**/*.js') 26 | .pipe(connect.reload()) 27 | }); 28 | 29 | gulp.task('html',function(){ 30 | gulp.src('./app/**/*.html') 31 | .pipe(connect.reload()) 32 | }); 33 | 34 | gulp.task('watch',function(){ 35 | gulp.watch('./dist/**/*.js',['js']); 36 | gulp.watch('./app/**/*.html',['html']); 37 | gulp.watch('./app/**/*.js',['browserify']); 38 | }); 39 | 40 | gulp.task('default',['browserify']); 41 | 42 | gulp.task('server',['browserify','connect','watch']); -------------------------------------------------------------------------------- /app/js/components/QuestionItem.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | module.exports = React.createClass({ 4 | voteUp:function(e){ 5 | var newCount = parseInt(this.props.voteCount + 1); 6 | this.props.onVote( this.props.questionKey, newCount ); 7 | }, 8 | voteDown:function(e){ 9 | var newCount = parseInt(this.props.voteCount - 1); 10 | this.props.onVote( this.props.questionKey, newCount ); 11 | }, 12 | render:function(){ 13 | return ( 14 |
15 |
16 | 20 | 23 |
24 |
25 |

{this.props.title}

26 |

{this.props.description}

27 |
28 |
29 | ) 30 | } 31 | }); -------------------------------------------------------------------------------- /app/js/components/QuestionForm.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | module.exports = React.createClass({ 4 | submitHandle:function(e){ 5 | e.preventDefault(); 6 | if(!this.refs.title.value) return ; 7 | 8 | var newQuestion = { 9 | title:this.refs.title.value, 10 | description:this.refs.description.value, 11 | voteCount:0, 12 | }; 13 | 14 | this.refs.addQuestionForm.reset(); 15 | 16 | this.props.onNewQuestion( newQuestion ); 17 | }, 18 | render:function(){ 19 | var styleObj = { 20 | display : this.props.formDisplay ? 'block':'none' 21 | }; 22 | return ( 23 |
24 |
25 | 26 | 27 |
28 | 29 | 30 | 31 |
32 | ) 33 | } 34 | }); -------------------------------------------------------------------------------- /app/js/containers/QuestionApp.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | var _ = require('lodash'); 3 | var ShowAddButton = require('../components/ShowAddButton'); 4 | var QuestionForm = require('../components/QuestionForm'); 5 | var QuestionList = require('./QuestionList'); 6 | 7 | module.exports = React.createClass({ 8 | getInitialState:function(){ 9 | var questions = [ 10 | { 11 | id:1, 12 | title:'产品经理与程序员矛盾的本质是什么?', 13 | description:'理性探讨,请勿撕逼。产品经理的主要工作职责是产品设计。接受来自其他部门的需求,经过设计后交付研发。但这里有好些职责不清楚的地方。', 14 | voteCount:10 15 | }, 16 | { 17 | id:2, 18 | title:'热爱编程是一种怎样的体验?', 19 | description:'别人对玩游戏感兴趣,我对写代码、看技术文章感兴趣;把泡github、stackoverflow、v2ex、reddit、csdn当做是兴趣爱好;遇到重复的工作,总想着能不能通过程序实现自动化;喝酒的时候把写代码当下酒菜,边喝边想边敲;不给工资我也会来加班;做梦都在写代码。', 20 | voteCount:8 21 | }, 22 | { 23 | id:3, 24 | title:'热爱编程是一种怎样的体验?', 25 | description:'别人对玩游戏感兴趣,我对写代码、看技术文章感兴趣;把泡github、stackoverflow、v2ex、reddit、csdn当做是兴趣爱好;遇到重复的工作,总想着能不能通过程序实现自动化;喝酒的时候把写代码当下酒菜,边喝边想边敲;不给工资我也会来加班;做梦都在写代码。', 26 | voteCount:5 27 | } 28 | ]; 29 | 30 | return { 31 | questions : questions, 32 | formDisplay:false 33 | } 34 | }, 35 | onToggleForm : function(){ 36 | this.setState({ 37 | formDisplay:!this.state.formDisplay 38 | }) 39 | }, 40 | onNewQuestion : function(newQuestion){ 41 | newQuestion.id = this.state.questions.length + 1; 42 | 43 | var newQuestions = this.state.questions.concat( newQuestion ); 44 | 45 | newQuestions = this.sortQuestion( newQuestions ); 46 | 47 | this.setState({ 48 | questions: newQuestions, 49 | }); 50 | }, 51 | onVote:function(key,newCount){ 52 | var questions = _.uniq(this.state.questions); 53 | var index = _.findIndex(questions,function(q){ 54 | return q.id == key; 55 | }); 56 | 57 | questions[index].voteCount = newCount; 58 | 59 | questions = this.sortQuestion( questions ); 60 | 61 | this.setState({ 62 | questions: questions, 63 | }); 64 | 65 | }, 66 | sortQuestion:function(questions){ 67 | questions.sort(function(a,b){ 68 | return b.voteCount - a.voteCount 69 | }); 70 | return questions 71 | }, 72 | render:function(){ 73 | return ( 74 |
75 |
76 |
77 |

React问答

78 | 79 |
80 |
81 |
82 | 86 | 89 |
90 |
91 | ) 92 | } 93 | }) 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 模仿知乎界面的一个简单React demo 2 | 3 | 博客地址:http://blog.xieliqun.com/2016/11/04/react-zhihu/ 4 | 5 | 6 | 7 | > 这是一个模仿知乎界面的简单React demo。这个React demo能让你从零开始学习React,并逐渐掌握React。它包括了一个项目从零到项目完成的整个过程。 8 | 9 | **项目运行** 10 | ```javascript 11 | $ git clone https://github.com/tsrot/react-zhihu.git 12 | $ cd react-zhihu 13 | 14 | $ npm install 15 | 16 | $ bower install 17 | 18 | $ gulp server //用浏览器打开 localhost:5000 19 | ``` 20 | 21 | 22 | ## 搭建开发环境 23 | 24 | ### 初始化npm bower 25 | 26 | ```javascript 27 | npm init //一直enter,默认就好 28 | 29 | bower init //同上 30 | 31 | ``` 32 | 33 | ### 安装必要的开发工具包 34 | 35 | - gulp :基于流的自动化构建工具 36 | - gulp-browserify :前端模块及依赖管理 37 | - gulp-concat :文件合并插件 38 | - gulp-react :JSX语法转化工具 39 | - gulp-connect :构建本地开发Web服务器 40 | - lodash :一个具有一致接口、模块化、高性能等特性的 JavaScript 工具库 41 | - reactify :React 编译器 42 | 43 | ```javascript 44 | npm install gulp gulp-browserify gulp-concat gulp-react gulp-connect lodash reactify --save-dev 45 | ``` 46 | 47 | ### 安装生产环境依赖包 48 | 49 | - react :主要框架 50 | - react-dom :React的DOM操作类 51 | - bootstrap :bootstrap样式 52 | 53 | ```javascript 54 | npm install --save react react-dom 55 | 56 | bower install --save bootstrap 57 | ``` 58 | 59 | ### 写入gulp配置文件gulpfile.js 60 | 61 | 你可以在npm的网站上找到相应插件的gulp配置写法。我配置的[gulpfile.js](https://github.com/tsrot/) 62 | 63 | 64 | ## 开发 65 | 66 | - 切分相应的模块 67 | - 分清UI组件和容器组件 68 | - 学会如何在组件之间通信 69 | - 注意写作规范和开发细节 70 | 71 | ## 部署生产 72 | 73 | 请切换分支到 product 分支 74 | 75 | ### 修改gulpfile文件 76 | ```javascript 77 | //添加copy任务 78 | gulp.task('copy',function(){ 79 | gulp.src('./app/css/*') 80 | .pipe(gulp.dest('./dist/css')); 81 | 82 | gulp.src('./bower_components/**/*') 83 | .pipe(gulp.dest('./dist/libs')); 84 | 85 | gulp.src('./*.html') 86 | .pipe(gulp.dest('./dist')); 87 | }); 88 | 89 | //生产服务器 90 | gulp.task('connect-pro',function(){ 91 | connect.server({ 92 | root:'./dist', 93 | port:port, 94 | livereload:true, 95 | }) 96 | }); 97 | 98 | //添加build任务 99 | gulp.task('build',['browserify','copy']); 100 | 101 | //添加启动生产服务器任务 102 | gulp.task('server-pro',['build','connect-pro','watch']); 103 | ``` 104 | 105 | ### 修改index.html引用目录 106 | ```javascript 107 | 108 | 109 | 110 | 111 | ``` 112 | 113 | ### 使用gulp-gh-pages部署到github pages 114 | 115 | 下载gulp-gh-pages插件 116 | ```javascript 117 | npm install --save-dev gulp-gh-pages 118 | ``` 119 | 在gulpfile文件中添加配置gulp-gh-pages代码 120 | ```javascript 121 | var ghPages = require('gulp-gh-pages'); 122 | 123 | gulp.task('deploy', function() { 124 | return gulp.src('./dist/**/*') 125 | .pipe(ghPages()); 126 | }); 127 | ``` 128 | 129 | ## webpack + es6 ([webpack分支](https://github.com/tsrot/react-zhihu/tree/webpack)) 130 | 131 | 1、手动删除bower_components,统一使用npm,这样有利于后面webpack的配置。 132 | 把bootstrap安装到node_modules: 133 | ```javascript 134 | $ npm install --save bootstrap 135 | ``` 136 | 2、安装webpack-stream、vinyl-named、gulp-clean 137 | ```javascript 138 | $ npm install --save-dev webpack-stream vinyl-named gulp-clean 139 | ``` 140 | 3、安装webpack plugin和webpack loader 141 | ```javascript 142 | $ npm install --save-dev html-webpack-plugin extract-text-webpack-plugin babel-core babel-loader babel-preset-es2015 babel-preset-react style-loader css-loader postcss-loader autoprefixer file-loader 143 | ``` 144 | 4、配饰gulp和webpack 145 | 146 | ## 后续 147 | 148 | 将在分支中更新使用下列技术的版本 149 | - webpack + es6 : [webpack分支](https://github.com/tsrot/react-zhihu/tree/webpack) 150 | - webpack + es6 + redux 151 | - webkack + es6 + redux + react-router 152 | 153 | --------------------------------------------------------------------------------