一个 PHPer 第一次用 Koa2 写 Node.js 的心路历程


学了一段时间的 js 了,突然想实践一下。正好公司有个小的项目要做,就顺手拿 Koa2 来做了。真是不做不知道,做了想不到。踩了一堆新手坑。

初次接触 Koa2

在知道 Koa2 之前,我也了解过 Express,可惜并没有实战用过。后来大家都说 Koa 是一个比 Express 更牛X的东西,于是在好(作)奇(死)心作祟下,直接去用 Koa2 了。后来证明的确是作死,原本用 PHP 一天就能写完东西,愣是让我搞了三天。

安装

最近 Node.js V8 发布了,原生支持 asyncawait 调用了,所以直接把 Node.js 升级了一下。

根据 Koa2 的教程,安装很简单,我是使用的 yarn 的(还真是比 npm 快)。

yarn add koa

默认就装了 Koa 2.2。然后装完了,其实我是一脸懵逼的,文档上说这样用。

const Koa = require('koa')
const app = new Koa()
 
// response 
app.use(ctx => {
  ctx.body = 'Hello Koa'
})
 
app.listen(3000)

我照着代码写了下来,的确成功了。可是,难不成我要把所有的逻辑写在 app.use 里?

一个 PHPer 第一次用 Koa2 写 Node.js 的心路历程

中间件

我感觉我受到了惊吓,吓得我赶紧往下看文档。原来 Koa2 是一个中间件模型。app.use 可以有很多,每一个 app.use 会注册一个中间件,这个中间件是具体做事情的。每个中间件是依次执行的。一个经典的洋葱图可以解释这一切。

一个 PHPer 第一次用 Koa2 写 Node.js 的心路历程

那么,上面的实例就可以改造成这样。

app.use(async (ctx, next) => {
  await next()
  ctx.body = 'Hello Koa'
})

按照上面的洋葱头,以心为单位,next的两侧的语句分别在洋葱的左侧和右侧进行执行,颇像 Laravel 的中间件。

就这样,我知道了,所有的操作不必写在同一个 app.use 里。可是,下一个问题来了,我要把所有的逻辑都写再一个文件里?说好的 MVC 呢?没有 MVC 也叫做框架?Are you kidding me?(好吧后来发现原来 Koa2 并不是一个装置做网站的框架)

既然没有 MVC,那就自己动手丰衣足食吧。

路由

首先要处理的就是路由的问题。不过,由于是第一次用这货写项目,时间紧,(伪)任务重,看了文档后发现,原来还有一个中间件列表的链接,里面有各种开源的中间件。我想你们一定隔着屏幕都能听到我发出杠铃般的笑声了。有一个中间件非常棒,叫做 koa-router。这货是这么用的。

var Koa = require('koa')
var Router = require('koa-router')
 
var app = new Koa()
var router = new Router()
 
router.get('/', function (ctx, next) {
  // ctx.router available
});
 
app.use(router.routes())

虽然是把逻辑和 app.use 分开了,但是,好像还是没有解决刚才的问题。说好的 MVC 也没有出现。于是我再去找了找,居然没有 Controller 的中间件。我一下就懵逼了,玩脱了?还有一天啊我的宝贝儿。经过我半秒钟的慎重思考,我还是用 koa-router 自己实现一个控制器吧。

Controller

const fs = require('fs')

function addRoutes(router, routes) {
  for (let route in routes) {
    switch (route.method) {
      case: 'post':
        router.post(route.uri, route.fn)
        console.log(`Register post url: ${route.uri}`)
        break
      case: 'get':
        router.get(route.uri, route.fn)
        console.log(`Register get url: ${route.uri}`)
        break
      default: 
        console.log(`Invalid url: ${route}`)
    }
  }
}

function addControllers(router) {
  let files = fs.readdirSync(__dirname + '/controllers')

  let controllerFiles = files.filter(f => {
    return f.endsWith('.js')
  })

  for (let controllerFile in controllerFiles) {
    console.log(`process controller: ${controllerFile}...`)
    let routes = require(__dirname + '/controllers')
    addRoutes(router, routes)
  }
}

module.exports = () => {
  let router = require('koa-router')()
  addControllers(router)
  return router.routes()
}

我通过在 controllers 文件夹中,创建若干 js 文件来作为 Controller 来使用。这里稍微参考了下 廖雪峰的文章

然后,我们只需要在 controllers 文件夹中添加合适的文件就可以了。例如我们添加一个文件叫做 chart.js ,然后这样写代码。

let hello = async (ctx, next) => {
  ctx.body = 'Hello the fucking world!'
}

module.exports = [
  {
    method: 'get',
    uri: 'hello',
    fn: hello,
  }
]

最后再在 app.js 注册中间件即可。

除此之外,我们还需要能够处理 ctx 里的内容,因为它里面存储的是原始的内容。还是由于时间紧,任(填)务(坑)重(急),我用了 koa-bodyparser

const bodyParser = require('koa-bodyparser')

app.use(bodyParser())

这里要提醒的是,这货一定要放在处理路由中间件的前面。

Model

MVCC 已经解决了,接下来就要解决 M 的问题了。这里我用的是 Sequelize。这个 ORM 和大多数的 ORM 都差不多,所以在这里这次没有踩到什么坑。我在根目录下新建了一个 config.js 的配置文件,然后新建了 model.js 用来定义模型。

const Sequelize = require('sequelize')
const config = require('./config').databases

...

module.exports = {
  //models
}

View

视图,我是使用了一个中间件叫做 koa-view。由于它使用的是 Nunjucks 模板引擎,对于写 PHP 的我相对熟悉一点。

const view = require('koa-view')

const app = Koa()

app.use(view(__dirname + '/views'))
//controller

let Hello = (ctx, next) => {
  ctx.render('hello', datas)
}

只要在 ‘views’ 文件夹中定义相对应的 html 文件即可。

后记

这次的尝试,终于在我的修修补补中,搞出了一个简陋的 MVC 模型。赶在了 deadline 前完成,真是一波三折啊。学习新技术,就是这样,要实践嘛= =下面给出我的项目目录作参考

koa2/
|
+- controllers/
|  |
|  +- chart.js
|  ...
|
+- static/
|  |
|  +- js/
|     ...
|  |
|  +- style/
|     |
|     +- img
|     ...
|
+- views/
|  |
|  +- game.html
|  ...
|
+- app.js
|
+- config.js
|
+- controller.js
|
+- model.js
|
+- package.json
|
+- yarn.lock
|
+- node_modules/

菜鸟作品,如有错误请指正,不胜感激。

如果你喜欢我的文章,那就请我喝杯奶茶吧~

一个 PHPer 第一次用 Koa2 写 Node.js 的心路历程


发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>