初见 Koa.js

去网上检索 Koa,往往会看到诸多 Koa 和 Express 文章。Koa 的确是比 Express 更新的框架,因此也使用到了 ES6 更新的特性,比如 async/await。Koa 的核心 module 仅仅是 middleware kernel,Express 则提供了一套完整的解决方案,功能,routing,template 这些。Koa 要使用这些需要安装额外的 module。这样的对比,容易让人联想到 editor 和 IDE 的区别,前者注重轻量,可定制,后者追求大而全的设计。两种不同的设计哲学,我是偏爱前者,相信 less is more 的力量。当然,毕竟 Koa 和 Express 都是来自于同一个开发团队,很多基础概念是相通的。阅读本文,你需要提前了解以下内容:

  • Node.js 的异步特性及异步是如何实现的
  • 异步实现的几种方式,callback 到 Promise 到 async/await
  • 什么是 middleware?
  • ejs template engine

通过本文,你能了解到。Koa 最基础的 HelloWorld,它 如何渲染一个 template 页面,传递数据。什么是「Routing 路由」,路由在 Koa 中如何实现的。

Hello world

app.use() 就是添加一个 middleware。我们通过 Koa 进行的许多操作,比如处理 request,处理 data,routing 都是通过 app.use() 来实现的。

ctx 内封装了 request 和 response object。

const Koa = require('koa');
const app = new Koa();

app.use(async function(ctx) {
    ctx.body = "hello world ssss";
})

app.listen(3000, function() {
    console.log('listen port: 3000...')
})

渲染 ejs 模版

这里以 ejs 为例来进行说明,其他的 template engine,使用方法都是相通的。

使用 npm 安装:

$ npm install koa-views --save
$ npm install ejs --save

server.js 内容是

const Koa = require('koa');
const app = new Koa();
const views = require('koa-views');

app.use(views(__dirname + '/views', {
    map: {
        html: 'ejs'
    }
}));

app.use(async function(ctx) {
    await ctx.render('layout.ejs');
})

app.listen(3000, function() {
    console.log('listen port: 3000...')
})

./views/layout.ejs 内容是

<!DOCTYPE html>
<head>

</head>
<body>
    <h1>Hello Koa, This is from ejs</h1>
</body>

上面这个例子是不包含传值的,当需要向 template 传递值时,通过 ctx.state 来设置,将上面 render 部分修改成:

app.use(async function(ctx) {
    ctx.state = {
        title: 'This is title',
        body: 'body bla bla'
    };
    await ctx.render('layout.ejs');
})

或者写成 render 的参数,二者是等价的:

app.use(async function(ctx) {
    await ctx.render('layout.ejs', {
        title: 'This is title',
        body: 'body bla bla'
    });
})

此时 template 修改成:

<!DOCTYPE html>
<head>

</head>
<body>
    <h1><%- title %></h1>
    <p><%- body %></p>
</body>

通常我们在写一个 template 的时候,会分成好多组件,首先有一个大体的框架,layout.ejs,新建一个 partials 文件夹,里面存储我们所需的各个组件,如 head.ejs,header.ejs,footer.ejs 等等。我们在一个需要渲染的页面里引用这些组件,那么这个过程在 koa 应该如何实现呢?

这里直接在 ejs 里使用 include 进行引用。

header.ejs

<header>
    <p>This is a header</p>
</header>

layout.ejs

<!DOCTYPE html>
<head>

</head>
<body>
    <%- include ./header %>
    <h1><%- title %></h1>
    <p><%- body %></p>
</body>

Router 路由功能

对于一个 web site,需要处理各种各样不同的请求,针对不同的请求 request,有着不同的反馈 response,以及可能要调用不同的资源 resource。有些需要调用一些 javascript 文件,css 文件,有些需要调用一些图片 images,有些需要访问数据库。这些不同的资源 resource 有着不同的存储路径,为了让 request 得到合适的反馈,就需要一个 router 路由功能,告诉 server,这个 request,需要去哪里找相应的 resource 去反馈。

koa_routing

$ npm install koa-router --save

server.js 中修改为:

const Router = require('koa-router');
const router = new Router()

router.get('/', async function(ctx) {
    await ctx.render('layout.ejs', {
        title: 'This is title',
        body: 'body bla bla'
    });
})

app.use(router.routes());

我们看到,各个页面的渲染完全由 router 进行了接管。

上面是最简单的 “Get” 请求,下面是给出一个”Post” 请求的例子,来自 koa2 进阶学习笔记,我做了一些小改动,原文使用的是原生 koa 中的 ctx 来判断请求。我这里直接使用了 koa-router 实现,通过对比,也可以明白 koa-router 这个 module 是如何工作的,只不过是在原生 Koa 基础上增加了一层判断。

// receive the posting data
function parsePostData(ctx) {
    return new Promise((resolve, reject) => {
        try {
            let postData = "";
            ctx.req.addListener('data', (data) => {
                postData += data;
            });
            ctx.req.addListener('end', () => {
                let parseData = parseQueryStr(postData);
                resolve(parseData);
            });
        } catch(err) {
            reject(err);
        }
    })
}

// convert the posting data to Object
function parseQueryStr(data) {
    let queryData = {};
    let queryStrList = data.split('&');
    for (let queryStr of queryStrList) {
        let itemList = queryStr.split('=');
        queryData[ itemList[0] ] = decodeURIComponent(itemList[1]);
    }

    return queryData;
}

app.use(views(__dirname + '/views', {
    map: {
        html: 'ejs'
    }
}));

router.get('/', async function(ctx) {
    await ctx.render('layout.ejs', {
        data: 'no data posted'
    });
})

router.post('/', async function(ctx) {
    let postData = await parsePostData(ctx);
    await ctx.render('layout.ejs', {
        data: JSON.stringify(postData)
    })
})

app.use(router.routes());

layout.ejs 添加一个可以提交的表格,注意表格的 methodPOSTaction 是根目录页面 "/"

<!DOCTYPE html>
<head>

</head>
<body>
    <%- include ./header %>
    <h1>koa2 request post demo</h1>
      <form method="POST" action="/">
            <p>userName</p>
            <input name="userName" /><br/>
            <p>nickName</p>
            <input name="nickName" /><br/>
            <p>email</p>
            <input name="email" /><br/>
            <button type="submit">submit</button>
      </form>
      <p><%- data %><p>
</body>

分类

标签