内容简介:此时状态有点像上学时写作文,开篇总是"拉"不出来,憋的难受。
此时状态有点像上学时写作文,开篇总是"拉"不出来,憋的难受。
从背景出发
前后端分离后,前端童鞋会需要处理一些node层的工作,比如模板渲染、接口转发、部分业务逻辑等,比较常用的框架有koa、koa-router等。
现在我们需要实现这样一个需求:
- 用户访问
/fe的时候,页面展示 hello fe - 用户访问
/backend的时候,页面展示 hello backend
你是不是在想,这需求俺根本不用 koa 、 koa-router ,原生的node模块就可以搞定。
const http = require('http')
const url = require('url')
const PORT = 3000
http.createServer((req, res) => {
let { pathname } = url.parse(req.url)
let str = 'hello'
if (pathname === '/fe') {
str += ' fe'
} else if (pathname === '/backend') {
str += ' backend'
}
res.end(str)
}).listen(PORT, () => {
console.log(`app start at: ${PORT}`)
})
复制代码
确实是,对于很简单的需求,用上框架似乎有点浪费,但是对于以上的实现,也有缺点存在,比如
- 需要我们自己去解析路径。
- 路径的解析和逻辑的书写耦合在一块。如果未来有更多更复杂的需求需要实现,那就gg了。
所以接下来我们来试试用 koa 和 koa-router 怎么实现
app.js
const Koa = require('koa')
const KoaRouter = require('koa-router')
const app = new Koa()
const router = new KoaRouter()
const PORT = 3000
router.get('/fe', (ctx) => {
ctx.body = 'hello fe'
})
router.get('/backend', (ctx) => {
ctx.body = 'hello backend'
})
app.use(router.routes())
app.use(router.allowedMethods())
app.listen(PORT, () => {
console.log(`app start at: ${PORT}`)
})
复制代码
通过上面的处理,路径的解析倒是给 koa-router 处理了,但是整体的写法还是有些问题。
- 匿名函数的写法没有办法复用
- 路由配置和逻辑处理在一个文件中,没有分离,项目一大起来,同样是件麻烦事。
接下来我们再优化一下,先看一下整体的目录结构
├──app.js // 应用入口 ├──controller // 逻辑处理,分模块 │ ├──hello.js │ ├──aaaaa.js ├──middleware // 中间件统一注册 │ ├──index.js ├──routes // 路由配置,可以分模块配置 │ ├──index.js ├──views // 模板配置,分页面或模块处理,在这个例子中用不上 │ ├──index.html 复制代码
预览一下每个文件的逻辑
app.js 应用的路口
const Koa = require('koa')
const middleware = require('./middleware')
const app = new Koa()
const PORT = 3000
middleware(app)
app.listen(PORT, () => {
console.log(`app start at: ${PORT}`)
})
复制代码
routes/index.js 路由配置中心
const KoaRouter = require('koa-router')
const router = new KoaRouter()
const koaCompose = require('koa-compose')
const hello = require('../controller/hello')
module.exports = () => {
router.get('/fe', hello.fe)
router.get('/backend', hello.backend)
return koaCompose([ router.routes(), router.allowedMethods() ])
}
复制代码
controller/hello.js hello 模块的逻辑
module.exports = {
fe (ctx) {
ctx.body = 'hello fe'
},
backend (ctx) {
ctx.body = 'hello backend'
}
}
复制代码
middleware/index.js 中间件统一注册
const routes = require('../routes')
module.exports = (app) => {
app.use(routes())
}
复制代码
写到这里你可能心里有个疑问?
一个简单的需求,被这么一搞看起来复杂了太多,有必要这样么?
答案是:有必要,这样的目录结构或许不是最合理的,但是路由、控制器、view层等各司其职,各在其位。对于以后的扩展有很大的帮助。
不知道大家有没有注意到路由配置这个地方
routes/index.js 路由配置中心
const KoaRouter = require('koa-router')
const router = new KoaRouter()
const koaCompose = require('koa-compose')
const hello = require('../controller/hello')
module.exports = () => {
router.get('/fe', hello.fe)
router.get('/backend', hello.backend)
return koaCompose([ router.routes(), router.allowedMethods() ])
}
复制代码
每个路由对应一个控制器去处理,很分离,很常见啊!!!这似乎也是我们平时在前端写vue-router或者react-router的常见配置模式。
但是当模块多起来的来时候,这个文件夹就会变成
const KoaRouter = require('koa-router')
const router = new KoaRouter()
const koaCompose = require('koa-compose')
// 下面你需要require各个模块的文件进来
const hello = require('../controller/hello')
const a = require('../controller/a')
const c = require('../controller/c')
module.exports = () => {
router.get('/fe', hello.fe)
router.get('/backend', hello.backend)
// 配置各个模块的路由以及控制器
router.get('/a/a', a.a)
router.post('/a/b', a.b)
router.get('/a/c', a.c)
router.get('/a/d', a.d)
router.get('/c/a', c.c)
router.post('/c/b', c.b)
router.get('/c/c', c.c)
router.get('/c/d', c.d)
// ... 等等
return koaCompose([ router.routes(), router.allowedMethods() ])
}
复制代码
有没有什么办法,可以让我们不用手动引入一个个控制器,再手动的调用koa-router的get post等方法去注册呢?
比如我们只需要做以下配置,就可以完成上面手动配置的功能。
routes/a.js
module.exports = [
{
path: '/a/a',
controller: 'a.a'
},
{
path: '/a/b',
methods: 'post',
controller: 'a.b'
},
{
path: '/a/c',
controller: 'a.c'
},
{
path: '/a/d',
controller: 'a.d'
}
]
复制代码
routes/c.js
module.exports = [
{
path: '/c/a',
controller: 'c.a'
},
{
path: '/c/b',
methods: 'post',
controller: 'c.b'
},
{
path: '/c/c',
controller: 'c.c'
},
{
path: '/c/d',
controller: 'c.d'
}
]
复制代码
然后使用 pure-koa-router 这个模块进行简单的配置就ok了
const pureKoaRouter = require('pure-koa-router')
const routes = path.join(__dirname, '../routes') // 指定路由
const controllerDir = path.join(__dirname, '../controller') // 指定控制器的根目录
app.use(pureKoaRouter({
routes,
controllerDir
}))
复制代码
这样整个过程我们的关注点都放在路由配置上去,再也不用去手动 require 一堆的文件了。
简单介绍一下上面的配置
{
path: '/c/b',
methods: 'post',
controller: 'c.b'
}
复制代码
path: 路径配置,可以是字符串 /c/b ,也可以是数组 [ '/c/b' ] ,当然也可以是正则表达式 /\c\b/
methods: 指定请求的类型,可以是字符串 get 或者数组 [ 'get', 'post' ] ,默认是 get 方法,
controller: 匹配到路由的逻辑处理方法, c.b 表示 controllerDir 目录下的 c 文件导出的 b 方法, a.b.c 表示 controllerDir 目录下的 /a/b 路径下的b文件导出的c方法
源码实现
接下来我们逐步分析一下实现逻辑
整体结构
module.exports = ({ routes = [], controllerDir = '', routerOptions = {} }) => {
// xxx
return koaCompose([ router.routes(), router.allowedMethods() ])
})
复制代码
pure-koa-router 接收
-
routes- 可以指定路由的文件目录,这样pure-koa-router会去读取该目录下所有的文件 (const routes = path.join(__dirname, '../routes'))
- 可以指定具体的文件,这样pure-koa-router读取指定的文件内容作为路由配置 const routes = path.join(__dirname, '../routes/tasks.js')
- 可以直接指定文件导出的内容 (const routes = require('../routes/index'))
-
controllerDir、控制器的根目录 -
routerOptionsnew KoaRouter时候传入的参数,具体可以看 koa-router
这个包执行之后会返回经过 koaCompose 包装后的中间件,以供koa实例添加。
参数适配
assert(Array.isArray(routes) || typeof routes === 'string', 'routes must be an Array or a String')
assert(fs.existsSync(controllerDir), 'controllerDir must be a file directory')
if (typeof routes === 'string') {
routes = routes.replace('.js', '')
if (fs.existsSync(`${routes}.js`) || fs.existsSync(routes)) {
// 处理传入的是文件
if (fs.existsSync(`${routes}.js`)) {
routes = require(routes)
// 处理传入的目录
} else if (fs.existsSync(routes)) {
// 读取目录中的各个文件并合并
routes = fs.readdirSync(routes).reduce((result, fileName) => {
return result.concat(require(nodePath.join(routes, fileName)))
}, [])
}
} else {
// routes如果是字符串则必须是一个文件或者目录的路径
throw new Error('routes is not a file or a directory')
}
}
复制代码
路由注册
不管routes传入的是文件还是目录,又或者是直接导出的配置的内容最后的结构都是是这样的
routes内容预览
[
// 最基础的配置
{
path: '/test/a',
methods: 'post',
controller: 'test.index.a'
},
// 多路由对一个控制器
{
path: [ '/test/b', '/test/c' ],
controller: 'test.index.a'
},
// 多路由对多控制器
{
path: [ '/test/d', '/test/e' ],
controller: [ 'test.index.a', 'test.index.b' ]
},
// 单路由对对控制器
{
path: '/test/f',
controller: [ 'test.index.a', 'test.index.b' ]
},
// 正则
{
path: /\/test\/\d/,
controller: 'test.index.c'
}
]
复制代码
主动注册
let router = new KoaRouter(routerOptions)
let middleware
routes.forEach((routeConfig = {}) => {
let { path, methods = [ 'get' ], controller } = routeConfig
// 路由方法类型参数适配
methods = (Array.isArray(methods) && methods) || [ methods ]
// 控制器参数适配
controller = (Array.isArray(controller) && controller) || [ controller ]
middleware = controller.map((controller) => {
// 'test.index.c' => [ 'test', 'index', 'c' ]
let controllerPath = controller.split('.')
// 方法名称 c
let controllerMethod = controllerPath.pop()
try {
// 读取/test/index文件的c方法
controllerMethod = require(nodePath.join(controllerDir, controllerPath.join('/')))[ controllerMethod ]
} catch (error) {
throw error
}
// 对读取到的controllerMethod进行参数判断,必须是一个方法
assert(typeof controllerMethod === 'function', 'koa middleware must be a function')
return controllerMethod
})
// 最后使用router.register进行注册
router.register(path, methods, middleware)
复制代码
源码的实现过程基本就到这里了。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Creative Curve
Allen Gannett / Knopf Doubleday Publishing Group / 2018-6-12
Big data entrepreneur Allen Gannett overturns the mythology around creative genius, and reveals the science and secrets behind achieving breakout commercial success in any field. We have been s......一起来看看 《The Creative Curve》 这本书的介绍吧!