详谈koa【koa语法 | koa/router | 中间件 | 洋葱模型】

koa是node的一个非常好用的小框架,koa很适合中小型项目。

nest也是node非常好用的框架,nest是大型框架,企业级别。以后我们再来详聊

今天就带大家认识下这个框架,学习下它的基础语法

准备工作

先初始化下

csharp 复制代码
npm init -y // 生成package.json文件  -y代表问题全部回答yes

好了,当前文件夹有了这个package.json项目说明书才能称之为一个项目,我们现在再安装下依赖

css 复制代码
npm i

这里用原生node简单模拟下后端启动一个一直运行的服务,提供接口给前端请求。

javascript 复制代码
const http = require('http')

const server = http.createServer((req, res) => { // (请求体,响应体)
    res.end('Welcome to Node Server')
})

server.listen(3000, () => { // 3000端口
    console.log('server listening on 3000')
})

res.end方法专门负责向前端响应内容

现在只要有前端向3000端口发请求,那么后端就会返回这么一句话

现在问题来了,如果前端很多个页面,每个页面都有请求,那么原生node的逻辑就会这样写,用if-else

javascript 复制代码
const http = require('http') 

const server = http.createServer((req, res) => {
    if(req.url.startsWith('/home')) {
        res.end('Welcome to the home page')  
    }else if(req.url.startsWith('/detail')) {
        res.end('Welcome to the detail page')
    }else {
        res.end('Not Found')
    }
})

server.listen(3000, () => { // 3000端口
    console.log('server listening on 3000')
})

我们通过if-else来提供不同的接口地址,前端请求home就走第一个的逻辑,正常来讲,里面的逻辑是检索数据库,拿到数据返回给前端,代码量会比较大。

如果项目很大,页面很多,并且一般每个页面都会有好多接口,那么if-else就会很多,代码将会非常不美观,不优雅

原生node不太好解决这个问题,但是第三方框架提供了这些解决办法,我们现在就来看看koa如何解决这个问题

安装koa

css 复制代码
npm i koa

koa目前是第二个版本,功能已经非常完善了,基本上不会更新到版本3

引入koa

ini 复制代码
const Koa = require('koa')

koa的源码将其定义成一个构造函数

我们现在用koa实现一个一直运行的后端

javascript 复制代码
const app = new Koa() // 建一个实例对象

app.listen(3000, () => { // 监听3000端口
    console.log('项目运行在3000端口')
})

和原生node差别不大,都是写listen监听,不过koa的listen和node不同

如何用koa提供接口呢,原生node是走createServer回调,而koa任意写一个函数即可,该函数需要被app use掉,并且函数一定会有一个参数,上下文对象,其实就是koa

javascript 复制代码
const main = (ctx) => {  // ctx == koa

}
app.use(main)

按道理来讲,后端应该有两个参数,一个请求体,一个响应体,后端需要判断前端发的什么请求,才能给到前端需要的内容,所以koa是将二者合并了

ctx.body

koa专门向前端反馈内容的一个属性,其实就是原生node的res.end

javascript 复制代码
const Koa = require('koa') 

const app = new Koa() 

const main = (ctx) => { 
    ctx.body = { 
        msg: 'Hello world'
    }
}

app.use(main)
app.listen(3000, () => { 
    console.log('项目运行在3000端口')
})

前端请求多个接口,koa怎么写?if-else依旧可以写

ini 复制代码
const main = (ctx) => { 
    if(ctx.url.startsWith('/home')) {
        ctx.body = 'Hello world'
    }else if(ctx.url.startsWith('/detail')) {
        ctx.body = 'detail'
    }else {
        ctx.body = 'Not Found' 
    }
}

假设刚好往前端输出的时候是个html语句,那么输出就会是将其以html的形式输出来

ini 复制代码
ctx.body = '<h2>Hello World</h2>'

因为后端输出的响应头格式默认就是text/html,响应头格式其实就是后端告诉前端应该如何加载内容

响应头格式是后端控制的,如何进行更改呢?

ini 复制代码
ctx.response.type = 'json' 
ctx.body = '<h2>Hello World</h2>' 

闲聊一会儿

我们再来模拟一个场景,自己简单写一个data.json文件,里面放一个json数据,然后让前端访问user地址的时候后端返回数据给前端

javascript 复制代码
const Koa = require('koa') 
const fs = require('fs') // 读取文件
const app = new Koa() 

const main = (ctx) => {
    if(ctx.url.startsWith('/user')) {
        const data = fs.readFileSync('./data.json', 'utf8')
        ctx.body = data
    }
}
app.use(main) 
app.listen(3000, () => { 
    console.log('项目运行在3000端口')
})

这里就是需要用上node的fs内置模块才能读取文件

假设前端访问home,后端自己写了一个html文件,并且输出给前端

javascript 复制代码
const Koa = require('koa') 
const fs = require('fs') // 读取文件
const app = new Koa() 

const main = (ctx) => {
    if(ctx.url.startsWith('/user')) {
        const data = fs.readFileSync('./data.json', 'utf8')
        ctx.body = data
    }else if (ctx.url.startsWith('/home')) {
        const page = fs.readFileSync('./template.html', 'utf8')
        ctx.body = page 
    }
}

app.use(main) 
app.listen(3000, () => { 
    console.log('项目运行在3000端口')
})

我们再去访问下home,结果发现,居然就是html渲染后的页面,而不是整个html代码

响应内容也是整个html代码

看到这个效果你就会清楚为什么有些人纯后端也可以写一个完整的项目,jsp其实就是这么干的,在html中写java

浏览器的默认行为就是遇到后端响应回来的内容是完整的html代码,会给它渲染完成,而不是当成字符串

当然你也可以改,直接加一句ctx.response.type = 'json',告诉浏览器将其当成json处理

以前项目开发就是这样的,前端写好页面给到后端,后端负责哪个接口读取哪些html文件给到用户展示,这就是前后端不分离的开发方式,效率很低,因为前端没有任何逻辑,全部丢给了后端,前端的工作一下子就可以完成

如今前后端分离的开发方式就是前端可以帮后端分担很多逻辑,提高开发效率

不用if-else而用koa/router

koa的路由就可以代替if-else。此路由非vue-router,现在看来路由好像都很强大,vue路由是负责在单页应用下什么路径展示什么组件,而这里的后端就是什么接口走什么逻辑

如同vue-routerkoa/router也需要安装

bash 复制代码
npm install @koa/router

由于koa/router源码很简单,许多人喜欢给它造轮子,导致你可能会找到野生的koa路由。这里丢一份官方地址:koa-router - npm (npmjs.com)

用法如下,需要引入,并且路由源码也是构造函数的形式,需要实例化

javascript 复制代码
const Koa = require('koa')
const Router = require('@koa/router') // 引入刚才安装的路由 
const router = new Router()
const app = new Koa() 

const main = (ctx) => {
    ctx.body = '首页页面'
}
const about = (ctx) => {
    ctx.body = '关于页面'
} 

router.get('/main', main) // 第一个参数命中才走第二个参数
router.get('/about', about)

app.use(router.routes()) // 所有配置的路由都生效
app.use(router.allowedMethods({})) // 允许所有的方法都生效,get,post,fetch等等

app.listen(3000, () => { 
    console.log('项目运行在3000端口')
})

这样,代码就比if-else优雅许多

日志

日志的作用就是调错,后端打印出来,我们看下koa如何打印日志,假设这里我们想要实现,不管前端请求什么接口,后端都能打印请求的接口,方法,时间

javascript 复制代码
const Koa = require('koa')
const Router = require('@koa/router')
const router = new Router()
const app = new Koa() 

const logger = (ctx, next) => { // 日志
    console.log(`${ctx.url} - ${ctx.method} - ${Date.now()}`)
    next()
}

const main = (ctx) => {
    ctx.body = '首页页面'
}
const about = (ctx) => {
    ctx.body = '关于页面'
} 

// main about不可能同时触发,所以不用next
router.get('/main', main)
router.get('/about', about)

app.use(logger)

app.use(router.routes()) 
app.use(router.allowedMethods({})) 

app.listen(3000, () => { 
    console.log('项目运行在3000端口')
})

这里注意,app.use默认情况下只会让第一个app.use的函数生效,想要第二个use也能生效,就必须在第一个函数加一个next()

你可能会疑惑,为何mainaboutuse不用加next(),这两个不可能同时存在,不会同时触发,就不需要写next()

安利一个插件nodemon

每当你修改后端代码的时候都需要重新node启动一下,有了nodemon就不需如此,保存之后就可以生效

全局安装

css 复制代码
npm i -g nodemon

使用

nodemon app.js

中间件

中间件其实就是个函数,只要被use掉了,那么这个函数就是个中间件

中间件执行是有规则的

javascript 复制代码
const fa = (ctx, next) => {
    console.log('a')
}
const fb = (ctx, next) => {
    console.log('b')
}

app.use(fa)
app.use(fb)

没有next的情况下只能触发第一个use,所以这里只打印a

javascript 复制代码
const fa = (ctx, next) => {
    console.log('a')
    next()
}
const fb = (ctx, next) => {
    console.log('b')
}

app.use(fa)
app.use(fb)

这里有next,因此这里打印ab

所以我们是可以控制函数的执行顺序的,因此也可以处理异步

我们再来看个输出

javascript 复制代码
const fa = (ctx, next) => {
    console.log('a')
    next()
    console.log('1')
}
const fb = (ctx, next) => {
    console.log('b')
    next()
    console.log('2')
}
const fc = (ctx, next) => {
    console.log('c')
    next()
    console.log('3')
}
app.use(fa)
app.use(fb)
app.use(fc)

next的源码就是用递归写的,执行的时候只要碰到next就回去执行下一个函数,最后打印完abc,也就是说执行完了第三个next,开始打印3,往回走

其实这个执行机制就是koa洋葱模型

很好理解,就相当于一根筷子直接穿过整个洋葱,从外向内捕获,然后从内向外冒泡

错误处理

常见错误处理就是throw,如下

javascript 复制代码
const Koa = require('koa')
const app = new Koa()

const main = (ctx) => {
    throw(500) 
}

app.use(main)
app.listen(3000, () => {
    console.log('项目运行在3000端口')
})

当然,这是人为故意写的错误

假设前端非要访问一个不存在的页面,我们返回一个404错误

ini 复制代码
const Koa = require('koa')
const app = new Koa()

const main = (ctx) => {
    ctx.response.status = 404
    ctx.body = 'Page not found'
}

app.use(main)
app.listen(3000, () => {
    console.log('项目运行在3000端口')
})

只要状态码不是200,接口都会报红

当然我们也可以写个函数集中处理错误,用try catch写,try里面要正常执行下面的函数,就需要写next(),由于报错也是异步的过程,我们也需要用async await将其屡成同步

javascript 复制代码
const Koa = require('koa')
const app = new Koa()

// 集中处理错误
const handle = async(ctx, next) => {
    try {
        await next()
    } catch (error) {
        console.log(error, '//////////')
        ctx.response.status = error || 500
        ctx.body = '<p>页面找不到了,请稍后</p>'
    }
}

const main = (ctx) => {
    throw(500)
}
app.use(handle)
app.use(main)
app.listen(3000, () => {
    console.log('项目运行在3000端口')
})

cookie是个本地存储,通常用于存放一些关键的key,比如登录凭证。cookie虽然是浏览器的功能,但是是后端控制的

我们登录淘宝网页,关掉浏览器再次进入不需登录,其实就是第一次登录时,淘宝后端给我们返回了一个加了密的密钥,证明账号的身份,第二次进入淘宝时淘宝会先读取你的key,没有就会让你重新登录

javascript 复制代码
const Koa = require('koa')
const app = new Koa()

const main = (ctx) => {
    const n = Number(ctx.cookies.get('view') || 0) + 1
    ctx.cookies.set('view', n) 
    ctx.body = n + 'view' 
}

app.use(main)
app.listen(3000, () => {
    console.log('项目运行在3000端口')
})

这个函数就是读取一个名为view的cookie,浏览一次,其值就会加1,看浏览次数

最后

有了koa,我们用node写后端就会方便许多,不再需要繁琐的if-else,直接用上koa的路由即可,如果你还可以连上数据库,从数据库拿到数据,那么后端你也就会了

另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请"点赞+评论+收藏"一键三连,感谢支持!

本次学习代码已上传至本人GitHub学习仓库:github.com/DolphinFeng...

相关推荐
夫琅禾费米线1 小时前
[有趣的JavaScript] 为什么typeof null返回 object
开发语言·前端·javascript
小镇程序员6 小时前
vue2 src自定义事件
前端·javascript·vue.js
灰色人生qwer8 小时前
快速删除 node_modules 目录的集中方法
node.js·node_modules
AlgorithmAce8 小时前
Live2D嵌入前端页面
前端
nameofworld8 小时前
前端面试笔试(六)
前端·javascript·面试·学习方法·递归回溯
哎呦没9 小时前
Spring Boot OA:企业办公自动化的高效路径
java·spring boot·后端
前端fighter9 小时前
js基本数据新增的Symbol到底是啥呢?
前端·javascript·面试
真心喜欢你吖9 小时前
Spring Boot与MyBatis-Plus的高效集成
java·spring boot·后端·spring·mybatis
2401_857636399 小时前
实验室管理技术革新:Spring Boot系统
数据库·spring boot·后端
2401_857600959 小时前
实验室管理流程优化:Spring Boot技术实践
spring boot·后端·mfc