• 关于React前端构建的一般过程


      

    概要

    本文以个人阅读实践经验归纳前端架构构建过程,以Step by Step方式说明创建一个前端项目的过程。并会对每个阶段所使用的技术进行可替代分析,如Express替换Hapi或者Koa的优缺点分析。本文仅供参考。

    流程

    1. Package.json

    首先,我们需要创建package.json文件。对设计初期已知的引用包和依赖包进行管理,使用ES6的,需要设置babel。其次编写脚本命令。一般文件形式如下:

    {
      "name": "practice",
      "description": "Ryan Project",
      "version": "1.0.0",
      "main": "server.js",
      "scripts": {
        "start": "node server.js",
        "watch": "nodemon server.js"
      },
      "babel": {
        "presets": [
          "es2015",
          "react"
        ]
      },
      "dependencies": {
        "alt": "^0.17.8",
        "async": "^1.5.0",
        "body-parser": "^1.14.1",
        "colors": "^1.1.2",
        "compression": "^1.6.0",
        "express": "^4.13.3",
        "history": "^1.13.0",
        "mongoose": "^4.2.5",
        "morgan": "^1.6.1",
        "react": "latest",
        "react-dom": "latest",
        "react-highcharts": "^10.0.0",
        "react-router": "^1.0.0",
        "request": "^2.65.0",
        "serve-favicon": "^2.3.0",
        "socket.io": "^1.3.7",
        "swig": "^1.4.2",
        "underscore": "^1.8.3",
        "xml2js": "^0.4.15"
      },
      "devDependencies": {
        "babel-core": "^6.1.19",
        "babel-preset-es2015": "^6.1.18",
        "babel-preset-react": "^6.1.18",
        "babel-register": "^6.3.13",
        "babelify": "^7.2.0",
        "bower": "^1.6.5",
        "browserify": "^12.0.1",
        "gulp": "^3.9.0",
        "gulp-autoprefixer": "^3.1.0",
        "gulp-concat": "^2.6.0",
        "gulp-cssmin": "^0.1.7",
        "gulp-if": "^2.0.0",
        "gulp-less": "^3.0.3",
        "gulp-plumber": "^1.0.1",
        "gulp-sourcemaps": "^1.6.0",
        "gulp-uglify": "^1.4.2",
        "gulp-util": "^3.0.7",
        "optimize-js": "^1.0.0",
        "vinyl-buffer": "^1.0.0",
        "vinyl-source-stream": "^1.1.0",
        "watchify": "^3.6.0"
      },
      "license": "MIT"
    }

    输入完成后,运行npm install,将package.json中的包安装到项目目录中,存放于对应node_modules文件夹

    2. Server.js

    即服务端,可以使用Express、Koa、Hapi等方式去创建服务端,设置服务端口。也可以设置socket相关的工作。

    Express创建服务端

    var express = require('express'); 
    var app = express();

    //创建路由
    app.get('/', function(req, res) {
      res.send('Hello world');
    });

    //创建REST API
    var router = express.Router();
    router.route('/items/:id')
    .get(function(req, res, next) {
      res.send('Get id: ' + req.params.id); })
    .put(function(req, res, next) {
      res.send('Put id: ' + req.params.id); })
    .delete(function(req, res, next) {   
      res.send('Delete id: ' + req.params.id); });
    app.use('/api', router);
    var server = app.listen(3000, function() {
      console.log('Express is listening to http://localhost:3000');
    });

    Koa创建服务端

    var koa = require('koa');
    var app = koa();
    //创建路由
    app.use(function *() {
      this.body = 'Hello world';
    });
    //创建REST API
    app.use(route.get('/api/items', function*() { this.body = 'Get'; }));
    app.use(route.post('/api/items', function*() { this.body = 'Post'; }));
    app.use(route.put('/api/items/:id', function*(id) { this.body = 'Put id: ' + id; }));
    app.use(route.delete('/api/items/:id', function*(id) { this.body = 'Delete id: ' + id; }));
    var server = app.listen(3000, function() { console.log('Koa is listening to http://localhost:3000'); });

    Hapi创建服务端

    var Hapi = require('hapi');
    var server = new Hapi.Server(3000);
    server.route({ 
      method: 'GET',
      path: '/',
      handler: function(request, reply) {
        reply('Hello world'); } });
    server.route([
      { method: 'GET', path: '/api/items', handler: function(request, reply) { reply('Get item id'); } },
      { method: 'GET', path: '/api/items/{id}', handler: function(request, reply) { reply('Get item id: ' + request.params.id); } },
      { method: 'POST', path: '/api/items', handler: function(request, reply) { reply('Post item'); } },
      { method: 'PUT', path: '/api/items/{id}', handler: function(request, reply) { reply('Put item id: ' + request.params.id); } },
      { method: 'DELETE', path: '/api/items/{id}', handler: function(request, reply) { reply('Delete item id: ' + request.params.id); } },
      { method: 'GET', path: '/', handler: function(request, reply) { reply('Hello world'); } } ]);
    server.start(
    function() { console.log('Hapi is listening to http://localhost:3000'); });

    三者间优缺点比较

      优点 缺点
    Express 庞大的社区,相对成熟。极易方便创建服务端,创建路由方面代码复用率高 基于callback机制,不可以组合使用,也不能捕获异常
    Koa

    相比Express,移除Route和View,中间件的使用移植和编写都比较方便,拥抱ES6,

    借助Promise和generator而非callback,能够捕获异常和组合使用

    以Express一样,需要routers中间件处理不同的选择
    Hapi  基于配置而非代码的框架,对于大型项目的一致性和可重用性比较有用。 为大型项目定制,导致在小项目中,常见的过于形式化的代码。相关的开源资料也比较少

    3. 工程化工具

    首先,我们需要先设计好我们项目的目录结构,以便使用工程化工作进行压缩打包等操作。

    简单举例如下项目的结构

    --/public
    --/css
    --/js
    --/fonts
    --/img
    --/app
        --/actions
        --/components
        --/stores
        --/stylesheets
            --main.less
        --alt.js
        --route.js
        --main.js

    其次,需要webpack或者browserify工具,打包压缩一系列的脚本文件。使用babel转换ES6语法,因为绝大部分的浏览器还不支持ES6,所以需要转换为ES5。最后,创建gulpfile.js文件,使用gulp创建系列的工程指令,如绑定vendor文件、引用sourcemap、使用类似uglify、gulp-cssmin等辅助压缩文件。

    如下是简易的gulpfile.js文件的配置

    var gulp = require('gulp');
    var gutil = require('gulp-util');
    var gulpif = require('gulp-if'); //conditionally run a task
    var autoprefixer = require('gulp-autoprefixer'); //Prefix CSS
    var cssmin = require('gulp-cssmin');
    var less = require('gulp-less'); //Less for Gulp
    var concat = require('gulp-concat');
    var plumber = require('gulp-plumber'); //Prevent pipe breaking caused by errors from gulp plugins
    var buffer = require('vinyl-buffer'); //convert streaming vinyl files to use buffers
    var source = require('vinyl-source-stream'); //Use conventional text streams at the start of your gulp or vinyl pipelines
    var babelify = require('babelify');
    var browserify = require('browserify');
    var watchify = require('watchify');
    var uglify = require('gulp-uglify'); //Minify files with UglifyJS.
    var sourcemaps = require('gulp-sourcemaps');
    
    var production = process.env.NODE_ENV === 'production'
    
    var dependencies = [
        'alt',
        'react',
        'react-dom',
        'react-router',
        'underscore'
    ]
    /*
     |--------------------------------------------------------------------------
     | Combine all JS libraries into a single file for fewer HTTP requests.
     |--------------------------------------------------------------------------
     */
    gulp.task('vendor', function () {
        return gulp.src([
            'bower_components/jquery/dist/jquery.js',
            'bower_components/bootstrap/dist/js/bootstrap.js',
            'bower_components/magnific-popup/dist/jquery.magnific-popup.js',
            'bower_components/toastr/toastr.js'
        ]).pipe(concat('vendor.js'))
        .pipe(gulpif(production, uglify({mangle: false})))
        .pipe(gulp.dest('public/js'))
    })
    /*
     |--------------------------------------------------------------------------
     | Compile third-party dependencies separately for faster performance.
     |--------------------------------------------------------------------------
     */
    gulp.task('browserify-vendor', function(){
        return browserify()
        .require(dependencies)
        .bundle()
        .pipe(source('vendor.bundle.js'))
        .pipe(buffer())
        .pipe(gulpif(production, uglify({mangle: false})))
        .pipe(gulp.dest('public/js'))
    })
    
    /*
     |--------------------------------------------------------------------------
     | Compile only project files, excluding all third-party dependencies.
     |--------------------------------------------------------------------------
     */
    gulp.task('browserify',['browserify-vendor'], function(){
        return browserify({entries:'app/main.js', debug: true})
        .external(dependencies)
        .transform(babelify, {presets: ['es2015','react']})
        .bundle()
        .pipe(source('bundle.js'))
        .pipe(buffer())
        .pipe(soucemaps.init({loadMaps: true}))
        .pipe(gulpif(production, uglify({mangle: false})))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest('public/js'))
    })
    
    /*
     |--------------------------------------------------------------------------
     | Same as browserify task, but will also watch for changes and re-compile.
     |--------------------------------------------------------------------------
     */
    gulp.task('browserify-watch', ['browserify-vendor'], function(){
        var bundler = watchify(browserify({ entries:'app/main.js', debug: true}), watchify.args)
        bundler.external(dependencies)
        bundler.transform(babelify, {presets: ['es2015', 'react']})
        bundler.on('update', rebundle)
        return rebundle()
    
        function rebundle() {
            var start = Date.now()
            return bundler.bundle()
            .on('error', function(err){
                gutil.log(gutil.colors.red(err.toString()))
            })
            .on('end', function() {
                gutil.log(gutil.colors.green(`Finished rebundling in ${(Date.now() - start)} ms`))
            })
            .pipe(source('bundle.js'))
            .pipe(buffer())
            .pipe(sourcemaps.init({loadMaps: true}))
            .pipe(sourcemaps.write('.'))
            .pipe(gulp.dest('public/js'))
        }
    })
    
    gulp.task('styles', function(){
        return gulp.src('app/stylesheets/main.less')
        .pipe(plumber())
        .pipe(less())
        .pipe(autoprefixer())
        .pipe(gulpif(production, cssmin()))
        .pipe(gulp.dest('public/css'))
    })
    
    gulp.task('watch', function(){
        gulp.watch('app/stylesheets/**/*.less', ['styles'])
    })
    
    gulp.task('default', ['styles','vendor','browserify-watch','watch'])
    gulp.task('build', ['styles', 'vendor', 'browserify'])
    View Code

    Gulp Task所做的操作如下说明:

    Gulp Task

    说明

    Vendor

    将所有第三方的js类库合并到一个文件

    Browserify-vendor

    将package.json中dependencies的依赖模块buffer化,以提供性能

    Browserify

    编译和绑定只与app相关的文件(无依赖项),并引用sourcemap对应、uglify压缩、buffer优化、babel转化ES6

    Browserify-watch

    利用watchify监测bundle.js文件的变化,并重新编译

    Styles

    编译less样式文件,自动添加前缀

    Watch

    监测Less文件,发生变化重新编译

    Default

    运行以上所有任务,且进程挂起监控watch

    Build

    运行以上所有任务,退出

    4. 其他包管理(可无)

    bower包管理工具的引入。由于NPM主要运用于Node.js项目的内部依赖包管理,安装的模块位于项目根目录下的node_modules文件夹内。并且采用嵌套的依赖关系树,即子依赖包各自有自己的依赖关系树,并不会造成他们之间的冲突。但是这种情况在纯前端的包管理就不那么友好了,比如你使用多个jquery版本。在使用方面npm主要用于管理类似grunt,gulp, ESlint,CoffeScript等npm模块。而bower管理纯前端css/js的包,比如jquery, bootstrap

    使用步骤

    1. 创建bower.json文件,将依赖包添加进(作用跟package.json类似)

    {
        "name": "practice",
        "dependencies": {
            "jquery": "^2.1.4",
            "bootstrap": "^3.3.5",
            "magnific-popup": "^1.0.0",
            "toastr": "^2.1.1"
        }
    }

    2. 运行

    npm install bower -g

    bower install

    5. 渲染部件 

    在渲染部分,React提供了客户端、服务端的渲染方式。具体区别如下:

    1. 客户端渲染:

      可以直接在浏览器运行ReactJS,这是通用的比较简单的方式,网上也有很多例子。http://reactjs.org。服务端只创建初始化的html,装载你的组件UI,提供接口和数据。前端做路由与渲染的工作。缺点就是用户等待时间长。

    2. 服务端渲染:

      html从后端生成,包含所有你的组件脚本UI以及数据。可以理解为生成一个静态的结果集页面。响应快,体验好。主要运用于提高主屏性能和SEO。服务端渲染,需要消耗CPU,但可以借助缓存实现优化。React中,通过renderToStaticMarkup方法实现。并且,你还需要保留对应的State以及所需要的数据。

    例子援引如下开源项目,有兴趣的朋友可以去了解下。

    http://sahatyalkabov.com/create-a-character-voting-app-using-react-nodejs-mongodb-and-socketio/ 

    以React-Router为例(客户端)

    1. 创建app/component/App.js

    首先创建组件的容器app,this.props.children用于渲染其他组件

    import React, {Component} from 'react'
    
    class App extends Component {
        render() {
            return (
                <div>
                    {this.props.children}
                </div>
            );
        }
    }
    
    export default App

    2. 创建app/routes.js

    如下点,指定路由/和/add,对应Home和AddCharacter组件

    import React from 'react'
    import {Route} from 'react-router'
    import App from './components/App'
    import Home from './components/Home'
    import AddCharacter from './components/AddCharacter';
    
    export default (
        <Route component ={App} >
            <Route path= '/' component={Home} />
            <Route path= '/add' component={AddCharacter} />
        </Route>
    )

    3.创建main.js

    将Router组合的组件渲染到id为app的div里。

    import React from 'react'
    import Router from 'react-router'
    import ReactDOM from 'react-dom'
    import { createHistory } from 'history'; // you need to install this package
    import routers from './routes'
    
    let history = createHistory();
    ReactDOM.render(<Router history={history}>
            {routers}
            </Router>, document.getElementById('app')) 

    5. app/components/添加home组件

    Import React from ‘react’
    Class Home extends React.Component{
        Render(){
            Return (
                <div className =’home’>
                    Hello </div>)
        } }
    Export default Home

    6. 组件 

    app/component/添加AddCharacter组件

    View Code

    这里采用的是alt(基于Flux)第三方库,所以还需要添加Actions和Store,以及alt.js文件。这里不一一列举,可以查看上面的源码地址。

    Tip: 也可以使用react-redux来构建我们自己的app组件,redux能更好的管理react的state。 

    7. 数据库

    创建数据库数据,如果你是单页应用,那么建议使用mongoDB。具体实现不再一一描述,可以上网搜索相关内容

    8. API 

    如果是基于mongoose的话,则只需要利用上面的Express、Koa或者Hapi创建API,访问mongoose数据.

    如果是大型项目,有自己独立的后端语言,如C#或者Java。则可以基于微服务框架创建服务API。使用axios或者superagent等库访问数据。

    参考文献

    http://sahatyalkabov.com/create-a-character-voting-app-using-react-nodejs-mongodb-and-socketio/

    http://stackoverflow.com/questions/27290354/reactjs-server-side-rendering-vs-client-side-rendering

    http://stackoverflow.com/questions/18641899/what-is-the-difference-between-bower-and-npm

    https://ifelse.io/2015/08/27/server-side-rendering-with-react-and-react-router/

    https://www.airpair.com/node.js/posts/nodejs-framework-comparison-express-koa-hapi

     

  • 相关阅读:
    Win10开始菜单中的天气不更新问题的解决方法
    Visual Studio 2017 的 JavaScript 调试功能的关闭
    Win10安装bash慢的解决方案
    关于 Google Chrome “Your connection is not private” 问题的处理
    关于 Inno Setup 报木马的问题处理
    Windows防火墙出站、入站相关知识总结
    关于Navicat Premium导入xlsx的问题
    关于VIM在Win10下的无意义折腾
    Mindjet MindManager 2016/2017 折腾记录
    2019腾讯前端技术大会资源TWeb
  • 原文地址:https://www.cnblogs.com/ruanyifeng/p/5891780.html
Copyright © 2020-2023  润新知