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

1,691 阅读4分钟

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

初次接触 Koa2

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

安装

最近 Node.js V8 发布了,原生支持 async 和 await 调用了,所以直接把 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 里?

中间件

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

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

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

MVC 的 C 已经解决了,接下来就要解决 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/

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

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