koa+ts 通过自动导入路由文件,通过路由装饰器注册路由

koa 类装饰器 路由装饰器 自动注册路由文件

koa 中如何优雅地定义路由?

最近在搞 nest.js,学习装饰器想起,以前刚接触 koa/experss 时,就想着怎么能把路由写的优雅一点

不想写一堆的router.get('/', (ctx, next) => {});当时只是简单的把文件分开

直到用了nest.js,如果在 koa/express 中也实现路由装饰器,岂不是也很好,就在此简单记录一下,koa 中实现路由装饰器

准备工作

先准备一个极简的 ts+koa 的基础项目

  1. 新建 koa-adorner 文件夹

  2. 初始化项目

npm init
  1. 安装依赖
npm i typescript ts-node-dev tslint @types/node -D
npm i koa koa-router
  1. 配置 scripts 启动项目
"start": "ts-node-dev ./src/index.ts -P tsconfig.json --no-cache"
  1. tslang配置 tsconfig.json 以支持装饰器
{
  "compilerOptions": {
    "module": "commonjs",
    "noImplicitAny": false,
    "removeComments": true,
    "preserveConstEnums": true,
    "sourceMap": true,
    "moduleResolution": "node",
    "experimentalDecorators": true, // 使用装饰器
    "allowSyntheticDefaultImports": true,
    "target": "es2017",
    "lib": ["es2015"],
    "outDir": "./dist",
    "typeRoots": ["./node_modules/@types"]
  },
  "include": ["src/**/*"]
}

  1. 创建 src/index.ts ,搭建一个简单的 http 服务内容如下

此时启动项目,访问 3000 端口可以看到响应了 get

import * as Koa from "koa";
import * as Router from "koa-router";

const app = new Koa();
const router = new Router(); // 实例化路由

router.get("/", async (ctx: Koa.Context) => {
  ctx.body = "get";
});
router.post("/", async (ctx: Koa.Context) => {
  ctx.body = "post";
});
app.use(router.routes());
app.listen(3000, () => {
  console.log("The service starts at port 3000");
});

在实际项目中,router 是复杂的,可能在 n 多个文件,然后 n 多个 router.get

而我们现在就是要消灭掉并且实现多文件自动加载,优雅得书写我们的路由文件

实现一个自动注册所有路由文件的方法

/src/utils/decorator.ts

import * as glob from "glob";

// 路由在此实例化
const router = new KoaRouter();

// 自动注册 {folder} 文件下的所有路由
export const load = (folder: string, options: LoadOptions = {}): KoaRouter => {
  const extname = options.extname || ".{js,ts}";
  glob
    .sync(require("path").join(folder, `./**/*${extname}`))
    .forEach((item) => require(item));
  return router;
};

/src/index.ts

注释原有的 router,使用 load 导入 src/routes 下的路由文件

import * as Koa from "koa";
// import * as Router from "koa-router";
import { load } from "./utils/decorator";
import { resolve } from "path";

const app = new Koa();
// const router = new Router(); // 实例化路由

// router.get("/", async (ctx: Koa.Context) => {
//   ctx.body = "get";
// });
// router.post("/", async (ctx: Koa.Context) => {
//   ctx.body = "post";
// });

const router = load(resolve(__dirname, "./routes"), {
  // extname: "",
});
app.use(router.routes());
app.listen(3001, () => {
  console.log("The service starts at port 3000");
});

创建一个路由文件

/src/routes/test.ts

此时我们期望这样书写即可完成路由的注册

其中@Controller("test")类装饰器将作为路由的前缀,我们可以在此组织相关模块的代码

@get("list") 也就是说我们可以通过 get 请求访问/test/list

@post() 如果不传参数,就默认获取add作为路径,也就是说我们可以通过 post 请求访问/test/add

import * as Koa from "koa";
import { Controller, get, post } from "../utils/decorator";

@Controller("test")
export default class testController {
  @get("list")
  public async list(ctx: Koa.Context) {
    const data = [{ id: 1, name: "测试" }];
    ctx.body = { ok: 1, data };
  }
  @post()
  public async add(ctx: Koa.Context) {
    const data = [{ id: 1, name: "测试" }];
    ctx.body = { ok: 1, data };
  }
}

实现 Controller 装饰器以及路由方法类

/src/utils/decorator.ts

同样在 decorator 中,我们新增如下代码

import * as Koa from "koa";
import * as KoaRouter from "koa-router";

type LoadOptions = {
  extname?: string,
};

type RouteOptions = {
  prefix?: string,
  middlewares?: Array<Koa.Middleware>,
};

type HTTPMethod = "get" | "post";

// Controller类装饰器 作用:给controller类添加路由前缀,当然愿意的话也可以不使用Controller类装饰器
export const Controller = (path = "") => {
  // 给controller类添加路由前缀
  return function(target) {
    target.prefix = "/" + path;
  };
};

// 方法装饰器
const decorate = (
  method: HTTPMethod,
  path: string,
  options: RouteOptions = {},
  router: KoaRouter
) => {
  return (target, property: string, descriptor) => {
    process.nextTick(() => {
      const { prefix = "" } = target.constructor; //Controller前缀
      const roterPath = "/" + (path || property); //去参数路径或者方法名作为路径

      // 添加中间件数组
      const middlewares = [];
      if (options.middlewares) {
        middlewares.push(...options.middlewares);
      }
      if (target.middlewares) {
        middlewares.push(...target.middlewares);
      }
      middlewares.push(target[property]);
      //router rul
      const url = options.prefix
        ? prefix + options.prefix + roterPath
        : prefix + roterPath;

      router[method](url, ...middlewares);
    });
  };
};
// method多包一层
const method = (method: HTTPMethod) => (
  path?: string,
  options?: RouteOptions
) => decorate(method, path, options, router);

// 中间件 改天专门写一下
export const middlewares = (middlewares: Koa.Middleware[]) => {
  return function(target: { prototype: { middlewares: any[] } }) {
    target.prototype.middlewares = middlewares;
  };
};

// 最终导出get、post ,此处以这两个为例
export const get = method("get");
export const post = method("post");

至此,我们在 koa 中已经简单得实现了路由装饰器,通过Controller @get等装饰器语法减去了重复繁琐的工作

也有利于我们组织代码与维护,其中跟多请求方式的,Controller 甚至根据类名来构建,请求数据校验等,可以根据业务进行完善,有时间再分享下 koa 的内容

你可能感兴趣的:(koa,node.js,javascript,vue.js)