Express

作者: whitsats | 来源:发表于2017-08-02 01:35 被阅读0次

    Express 框架

    第一章 Express简介

    npm提供了大量的第三方模块,其中不乏许多Web框架,我们没有必要重复发明轮子,因而选择使用 Express作为开发框架,因为它是目前最稳定、使用最广泛,而且Node.js官方推荐的唯一一个 Web开发框架。
    Express是一个轻量级的Web框架,多数功能只是对HTTP协议中常用操作的封装,更多的功能需要插件或者整合其他模块来完成.

    Express中文官网 http://www.expressjs.com.cn/

    第二章 初始化一个Express应用

    1. 我们想要安装Express,首先需要安装Epxress生成器,在全局通过命令安装
    npm install express-generator -g
    
    1. 等待安装完毕,查看帮助信息:
    express --help
    

    可以看到,

    Usage: express [options] [dir]
    

    我们可以用express 在当前目录创建dir,并且依据配置选项
    我们可以通过option设置express使用的模板,这里我们用ejs,也可以设置css的引擎,比如less和sass,这里我们先使用原生的css
    所以只需要输入

    express myapp --view ejs
    

    express会给我们快速的搭建一个app框架,然后根据提示进入myapp文件夹,运行

    npm install
    

    npm会自动帮助我们安装当前应用所需要的模块,等待安装完毕之后,启动:

    npm start
    

    浏览器打开:"localhost:3000",可以看到express框架给我返回的信息。

    在调试过程中,如果我们不想反复的重启服务端,可以npm安装nodemon,使用nodemon启动express项目

    第三章 目录分析

    回到我们安装express之前,看一下express快速搭建的目录结构:

     create : taobao
       create : taobao/package.json //依赖关系文件
       create : taobao/app.js // 入口文件
       create : taobao/public //公共资源文件夹
       create : taobao/public/images
       create : taobao/public/stylesheets
       create : taobao/public/stylesheets/style.css
       create : taobao/public/javascripts
       create : taobao/views //视图文件夹
       create : taobao/views/index.ejs
       create : taobao/views/error.ejs
       create : taobao/routes //路由文件夹
       create : taobao/routes/index.js
       create : taobao/routes/users.js
       create : taobao/bin
       create : taobao/bin/www //配置文件
    
    

    由此可见,express是一个标准的MVC框架,通过路由分配视图,并且可以在路由中写上我们的控制结构.
    修改 routes下的index.js

    router.get('/', function(req, res, next) {
      res.render('index', { title: '你好express' });
    });
    

    发现我们的页面出现了改变,这个路由跟我们自己写的路由作用是差不多的,具体用法我们后边再谈.

    第四章 模板的使用方式

    我们在安装express的时候,使用的模板是ejs,具体什么是模板呢?ejs又怎么用呢?
    简单来讲,模板就是帮助我们在view层进行输出的工具.我们打开view下的index.ejs,可以看到,这个模板其实就是在ejs下写了我们常见的html代码,只是多了几个不一样的标签:

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
      </head>
      <body>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %></p>
      </body>
    </html>
    
    

    <%= title %>可以帮助我们将路由中的{ title: '你好express' }输出到模板当中,其中的render就是模板渲染的方法,帮助我们渲染第一个参数index所对应的模板.类似angular,我们也可以在标签内部写自己的表达式

    <h1><%= title.toUpperCase() %></h1>
    

    4.1 模板引擎的用法

    ejs有三种常用的标签

    1. <% code %>:运行 JavaScript 代码,不输出
    2. <%= code %>:显示转义后的 HTML内容
    3. <%- code %>:显示原始 HTML 内容

    注意:<%= code %><%- code %>都可以是JavaScript表达式生成的字符串,当变量code为普通字符串时,两者没有区别。当 code 比如为 <h1>hello</h1> 这种字符串时,<%= code %> 会原样输出 <h1>hello</h1>,而 <%- code %> 则会显示H1大的hello字符串

    下面的例子可以帮我们解释<% code %>的用法:

    index.ejs

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
      </head>
      <body>
        <h1><%= title %></h1>
        <ul>
        <% for(var i=0; i<contents.length; i++) { %>
            <li><%= contents[i] %></li>
        <% } %>
        </ul>
      </body>
    </html>
    

    index.js

    router.get('/', function(req, res, next) {
        res.render('index',{ 
            title: 'express',
            contents:['AngularJS','Node.js','Express']
        });
    });
    

    查看渲染之后的结果.
    同样,如果你想要动态的调整js代码,可以将js代码在<script>标签中按照原样渲染.

    4.3 渲染模板的方法

    我们刚刚看到,routes下的index.js使用render可以将后边的参数渲染到模板之中,但是,我们在views下无法看到users.ejs,这是因为在user.js下使用了send方法,可以将后边的信息直接渲染而不通过模板.

    4.4 includes

    我们使用模板引擎通常不是一个页面对应一个模板,这样就失去了模板的优势,而是把模板拆成可复用的模板片段组合使用,如在 views 下新建 header.ejs 和 footer.ejs,并修改users.ejs

    views/header.ejs

    <!DOCTYPE html>
    <html>
      <head>
        <style type="text/css">
          body {padding: 50px;font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;}
        </style>
      </head>
      <body>
    

    views/footer.ejs

      </body>
    </html>
    

    views/users.ejs

    <%- include('header') %>
      <h1><%= name.toUpperCase() %></h1>
      <p>hello, <%= name %></p>
    <%- include('footer') %>
    

    routes/users.js

    router.get('/', function(req, res, next) {
      // res.send('respond with a resource');
      res.render('users',{ 
            name: 'express'
        });
    });
    

    访问'localhost:3000/users'可以看到渲染后的结果.
    我们将users.ejs拆成出了header.ejs和footer.ejs,并在users.ejs通过 ejs 内置的 include方法引入,从而实现了跟以前一个模板文件相同的功能.

    注意:要用 <%- include('header') %> 而不是 <%= include('header') %>

    第五章 路由

    我们在路由中将数据渲染并且分配到模板当中,那么我们如何写一个路由呢?

    5.1 入口文件

    首先,我们先要了解入口文件的组成,打开app.js,我们可以看到

    var express = require('express');
    var path = require('path');
    var favicon = require('serve-favicon');
    var logger = require('morgan');
    var cookieParser = require('cookie-parser');
    var bodyParser = require('body-parser');
    

    这几句话表示我们引入的模块

    var index = require('./routes/index');
    var users = require('./routes/users');
    

    路由都是模块写法,而入口文件将这些路由引入,我们就可以直接使用了.

    app.use('/', index);
    app.use('/users', users);
    

    这则使用了我们的路由

    // view engine setup
    app.set('views', path.join(__dirname, 'views'));
    app.set('view engine', 'ejs');
    

    这两句话设置了我们的模板引擎,如果我们想要使用sass或者less等css引擎,同样可以向下追加.

    // catch 404 and forward to error handler
    ...
    // error handler
    ...
    

    剩下的部分则是规定了404和500错误下的页面渲染方法.
    由此,我们可以在routes下添加的路由文件,并且在入口文件引入

    5.2 新增路由

    在routes下添加新的路由:
    routers/tests.js

    var express = require('express');
    var router = express.Router();
    router.get('/', function(req, res, next) {
       res.send('这是新的路由');
    });
    module.exports = router;
    

    同样,在入口文件添加
    app.js

    var tests = require('./routes/tests');
    app.use('/tests', tests);
    

    访问'localhost:3000/tests'则可以获取我们新增加的路由信息.

    5.3 路由中的参数

    其中 req,res是我们熟悉的请求和响应对象,当我们的地址栏访问模板的时候,可以获取参数

    ruoter.get('/:name', function(req, res, next) {
      res.send('hello, ' + req.params.name);
    });
    

    我们在地址栏请求'localhost:3000/tests/Y18',我们可以看到响应信息

    hello, Y18
    

    不难看出:req 包含了请求来的相关信息,res 则用来返回该请求的响应。下面介绍几个常用的

    req 的属性:

    • req.query: 解析后的 url 中的 querystring,如 ?name=haha,req.query 的值为 {name: 'haha'}
    • req.params: 解析 url 中的占位符,如 /:name,访问 /haha,req.params 的值为 {name: 'haha'}
    • req.body: 解析后请求体,需使用相关的模块,如 body-parser,请求体为 {"name": "haha"},则 req.body 为 {name: 'haha'},我们可以用接受post请求为例.
    • 注意,无论是send或者render,我们只能获取第一次符合标准的渲染页面

    5.4 next和中间件

    前面我们讲解了express中路由和模板引擎ejs的用法,但express的精髓并不在此,在于中间件的设计理念。

    我们可以看到,在路由的回调函数中,有一个参数next,这就是我们使用中间件最重要的部分。
    express中的中间件(middleware)是用来处理请求的,当一个中间件处理完,可以通过调用 next() 传递给下一个中间件,如果没有调用 next(),则请求不会往下传递,如内置的 res.render 其实就是渲染完 html 直接返回给客户端,如果调用了next(),那么请求会继续向下传递,访问路由组中的下一个路由。

    next中间件分为应用级中间件和路由级中间件

    应用级中间件

    我们回头再来看一下入口文件:

    app.use(logger('dev'));
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: false }));
    app.use(cookieParser());
    app.use(express.static(path.join(__dirname, 'public')));
    
    app.use('/', index);
    app.use('/users', users);
    app.use('/tests', tests);
    

    其实,这些app.use()也是给我们加载中间件,只是这些中间件是在应用中使用的,应用级中间件绑定到 app 对象 使用 app.use() 和 app.METHOD(), 其中, METHOD 是需要处理的 HTTP 请求的方法,例如 GET, PUT, POST 等等,全部小写。我们自定义一下全局中间件:

    // 挂载到全局的中间件,任何请求都会执行它
    app.use(function (req, res, next) {
      console.log('Time:', Date.now());
      next();
    });
    // 挂载至 /hello/:name 的中间件,任何指向 /hello/:name 的请求都会执行它
    app.use('/hello/:name', function (req, res, next) {
      console.log('Request Type:', req.method);
      next();
    });
    // 路由和句柄函数(中间件系统),处理指向 /hello/:name 的 GET 请求
    app.get('/hello/:name', function (req, res, next) {
      res.send('hello '+ req.params.name);
    });
    

    如果我们不指定路径,则默认匹配所有路径,我们在最后一个路由中并没有使用next,所以请求没有向下传递.
    我们在上方引入的其余全局中间件,即第三方中间件,其实也在回调函数的末尾使用了next方法,使得请求能够继续向下传递.

    在我们访问'localhost:3000/hello/Y18',可以在控制台和页面都能看到数据返回.

    注意:

    作为中间件系统的路由句柄,使得为路径定义多个路由成为可能。

    // 一个中间件栈,处理指向 /user/:id 的 GET 请求
    app.get('/hello/:id', function (req, res, next) {
      console.log('ID:', req.params.id);
      next();
    }, function (req, res, next) {
      res.send('hello Info');
    });
    
    // 处理 /user/:id, 打印出用户 id
    app.get('/hello/:id', function (req, res, next) {
      res.end(req.params.id);
    });
    

    但是由于第一个路由已经终止了请求-响应循环,第二个路由虽然不会带来任何问题,但却永远不会被调用.
    如果需要在中间件栈中跳过剩余中间件,调用 next('route') 方法将控制权交给下一个路由

    // 一个中间件栈,处理指向 /user/:id 的 GET 请求
    app.get('/hello/:id', function (req, res, next) {
      console.log('ID:', req.params.id);
      if(req.params.id == 4) next('route');
      else next();
    }, function (req, res, next) {
      res.send('hello Info');
    });
    
    // 处理 /user/:id, 打印出用户 id
    app.get('/hello/:id', function (req, res, next) {
      res.end(req.params.id);
    });
    

    路由级中间件

    路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()

    修改index.js

    var express = require('express');
    var router = express.Router();
    
    /* GET home page. */
    router.get('/:name', function(req, res, next) {
        // 如果 user id 为 0, 跳到下一个路由
        if (req.params.name != 'Y18') next('route');
        // 否则将控制权交给栈中下一个中间件
        else next(); 
    }, function (req, res, next) {
        // 渲染常规页面
        res.send('你别来了,Y18');
    });
    // 处理name为Y18的路由, 渲染一个特殊页面
    router.get('/:name', function (req, res, next) {
      res.send('欢迎你~ '+ req.params.name);
    });
    
    module.exports = router;
    

    错误处理中间件

    当我们访问一个不存在的路由时,虽然我们最后一个路由没有写next中间件,但是错误路由依旧执行,查看源码:

    https://github.com/expressjs/express/blob/master/lib/router/index.js

    function next(err) {
        ... //此处源码省略
        // find next matching layer
        var layer;
        var match;
        var route;
    
        while (match !== true && idx < stack.length) {
          layer = stack[idx++];
          match = matchLayer(layer, path);
          route = layer.route;
    
          if (typeof match !== 'boolean') {
            // hold on to layerError
            layerError = layerError || match;
          }
    
          if (match !== true) {
            continue;
          }
          ... //此处源码省略
        }
      ... //此处源码省略
        // this should be done for the layer
        if (err) {
            layer.handle_error(err, req, res, next);
        } else {
          layer.handle_request(req, res, next);
        }
      }
    

    next函数内部有个while循环,每次循环都会从stack中拿出一个layer,这个layer中包含了路由和中间件信息,然后就会用layer和请求的path就行匹配,如果匹配成功就会执行layer.handle_request,调用中间件函数。但如果匹配失败,就会循环下一个layer(即中间件)。

    express有成百上千的第三方中间件,在开发过程中我们首先应该去npm上寻找是否有类似实现的中间件,尽量避免造轮子,节省开发时间。下面给出几个常用的搜索 npm 模块的网站:

    1. http://npmjs.com(npm 官网)
    2. http://node-modules.com
    3. https://npms.io
    4. https://nodejsmodules.org

    第六章 在express中使用less或sass

    如果我们想要使用sass或者less,首先要对其进行学习;

    Express支持sass,less等css引擎,我们现在使用sass编译模板的样式:
    重新安装一个express应用:express app --view ejs -c sass
    我们可以看到在入口文件中看到变化:

    app.use(require('node-sass-middleware')({
      src: path.join(__dirname, 'public'),
      dest: path.join(__dirname, 'public'),
      indentedSyntax: true,
      sourceMap: true
    }));
    

    这其实就是引入了node的sass中间件。
    同时,在public/stylesheets中可以看到style.sass这个sass文件。
    启动express,访问'localhost:8000/'我们可以看到在这个文件夹中生成了两个新的文件:
    'style.css'
    'style.css.map'
    这就是我们熟悉的sass经过编译后的文件。
    同样,我们可以写自己的sass文件,决定样式:
    修改style.sass

    $fontStack:    Helvetica, sans-serif;
    $primaryColor: #8A2BE2;
    
    body
        font-family: $fontStack;
        padding: 50px;
    p
      color: $primaryColor;
    
    ul li
         color: #BDB76B;
    
    a
      text-decoration: none;
    

    修改index.ejs

    <!DOCTYPE html>
    <html>
      <head>
        <title><%= title %></title>
        <link rel='stylesheet' href='/stylesheets/style.css' />
      </head>
      <body>
        <h1><%= title %></h1>
        <p>Welcome to <%= title %></p>
        <ul>
            <li>AngularJS</li>
            <li>Node.js</li>
            <li>Express</li>
        </ul>
        <a id="test" href="www.zixue.it">自学it网</a>
      </body>
    </html>
    

    访问'localhost:3000',可以看到我们修改之后的样式.

    我们可以修改indentedSyntax改为false,实现scss方式的书写

    相关文章

      网友评论

        本文标题:Express

        本文链接:https://www.haomeiwen.com/subject/aazslxtx.html