持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第9天,点击查看活动详情
王志远,微医前端技术部
先看应用
应用规则:
- 创建子路由系统从而进行解耦合,项目更加模块化
const express = require('express');
const app = express();
const user = express.Router();
user.get('/add',function (req,res){
res.end('user add')
})
user.get('/remove',function (req,res){
res.end('user remove')
})
const article = express.Router();
article.get('/add',function (req,res){
res.end('article add')
})
article.get('/remove',function (req,res){
res.end('article remove')
})
app.use('/user', user);
app.use('/article', article);
app.listen(3000)
实现思路
首先,得明白它是一个中间件,这也就意味着express.Router返回的是一个函数;且它是路由,意味着Router函数不仅可以被new还得支持直接执行时返回一个路由系统。其二,子路由需要拼接父路由在进行匹配
express.Router返回的是一个函数,同时被 new 时又得返回一个对象
那我们就可以根据【函数被 new 时如果存在返回值且是一个引用类型的话,则返回此引用类型对象】的特点,首先定义Router的返回值是一个中间件函数;然后定义一个对象,将所有之前的属性和方法放在这个对象身上,将这个对象放置在中间件函数的原型链上;这样就实现了new和直接执行返回值都符合的情况。
实现父子路径拼接
在请求到来时,会符合中间件的匹配逻辑,这时我们直接执行子路由的handle从而让请求进入子路由的处理中;
注意一点,子路由中定义的是不包含前缀的路径,所以需要记录下中间件的path,从而先截取,再匹配;
具体实现
express.Router返回的是一个函数,同时被 new 时又得返回一个对象
const url = require('url');
const Layer = require('./layer');
const Route = require('./route');
const methods = require('methods');
function Router() { // 能充当构造函数 也可以充当类 , 无论是 new 还是 call 都返回一个函数
router.stack = [];
router.methods = {};
let router = (req,res,next) => {
router.handle(req,res,next)
}
router.removed = ""
router.__proto__ = proto;
return router
}
let proto = {};
// 外层的 layer 考虑路径 里层的 layer 考虑方法 = 同一个类
proto.prototype.route = function(path) {
let route = new Route();
let layer = new Layer(path, route.dispatch.bind(route)); // 每次调用 get 方法, 都会产生一个 layer 实例和一个 route 实例
// 这个关联目的是可以在 layer 获取 route 的信息
layer.route = route; // 路由中的 layer 都有一个 route 属性 和 我们的 route 关联起来
this.stack.push(layer)
return route;
}
proto.prototype.use = function(path, ...handlers) {
if (!handlers[0]) { // 只传递了一个函数
handlers.push(path); // app.use(function(){}) app.use()
path = '/'
}
handlers.forEach(handler => {
let layer = new Layer(path, handler);
layer.route = undefined; // 不写也是 undefined , 主要告诉你 中间件没有 route
this.stack.push(layer);
})
}
// app.get
methods.forEach(method => {
// app.get => handlers rouer.get
proto.prototype[method] = function(path, handlers) { // handlers 是用户定义 get 时传递过来的所有执行函数 (数组)
if(!Array.isArray(handlers)){
handlers = Array.from(arguments).slice(1);
}
let route = this.route(path); // 创建一个 route 实例
// 创建一个 layer 还要创建一个 route,将 handlers 传递给 route
route[method](handlers);
}
})
proto.prototype.handle = function(req, res, done) {
let { pathname } = url.parse(req.url);
let method = req.method.toLowerCase()
let idx = 0;
let removed = '';
const next = (err) => { // 中间件 和内部的 next 方法 出错都会走这个 next
if (idx >= this.stack.length) return done(); // 路由处理不了 传递给应用层
let layer = this.stack[idx++];
if(removed.length){
req.url = removed + req.url;
removed = '';
}
if (err) {
// 如果有错误 , 找错误处理中间件
if(!layer.route){ // 中间件
if(layer.handler.length === 4){
layer.handler(err,req,res,next)
}else{
next(err);
}
}else{ // 路由
next(err);
}
} else {
// 无论是路由还是中间件 前提是路径必须匹配
if (layer.match(pathname)) { // match 还没有更改
req.params = layer.params
if (!layer.route) { // 没有说明是中间件
// 正常中间件不走错误
if(layer.handler.length !== 4){
// 进入到中间件的时候 需要将中间件的路径移除掉
//add
if(layer.path !== '/'){
removed = layer.path; // 要删除的部分 中间件要是/ 就不要删除了
req.url = req.url.slice(layer.path.length) ;
}
layer.handle_request(req, res, next); // 直接执行中间件函数
}else{
next();
}
} else {
// 路由必须匹配方法
if (layer.route.methods[method]) { // 这个 next 可以让路由层扫描下一个 layer
layer.handle_request(req, res, next); // route.dispatch
} else {
next();
}
}
} else {
next();
}
}
}
next(); // 请求来了取出第一个执行
}
module.exports = Router;