У меня есть плагин для Express

Node.js TypeScript

Адрес исходного кода этого проекта:GitHub.com/Ping An8787/…

По мере того, как Nodejs охватывает все больше и больше областей пользовательского интерфейса и становится все более и более зрелым, я считаю, что многие друзья уже пробовали или использовали Nodejs для разработки серверных проектов. В этой статье я рассмотрю с вами Express, а затем представлю супер-плагин -OvernightJS, его сила в том, что он обеспечит поддержку декоратора TypeScript для экспресс-маршрутизации, упрощая нам разработку маршрутизации и повышая возможность повторного использования кода. Я также надеюсь помочь вам глубже понять декораторы TypeScript.

Теперь давайте посмотрим на этот плагин вместе с Лео, главным героем этой статьи~

1. Введение

В последнее время Лео намерен использоватьExpressДавайте начнем перестраивать серверный проект его блога.После тщательного исследования и дизайна, и окончательного утверждения плана, Лео приступает к работе:

// app.ts

import express, { Application, Request, Response } from 'express';

const app: Application = express();

app.get('/', (req: Request, res: Response) => {
  res.send('Hello World!');
});

app.listen(3000, ()=> {
  console.log('Example app listening on port 3000!');
});

Конфигурация tsconfig.json выглядит следующим образом:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
		"esModuleInterop": true,
    "experimentalDecorators": true, // 开启装饰器
    "emitDecoratorMetadata": true,  // 开启元编程
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

После написания базового кода можно запустить тест. Лео используется в командной строкеts-nodeвыполнение командной строки. (ts-nodeОн используется для непосредственного запуска файла ts. Подробную информацию см. в документации. Здесь я не буду вдаваться в подробности):

$ ts-node app.ts

Смотрите вывод командной строки:

Example app listening on port 3000!

Служба была в рабочем состоянии, в хорошем настроении. Затем Лео пишет другие интерфейсы, используя метод маршрутизации Express:

// app.ts

app.get('/article', (req: Request, res: Response) => {res.send('Hello get!')});
app.post('/article', (req: Request, res: Response) => {res.send('Hello post!')});
app.put('/article', (req: Request, res: Response) => {res.send('Hello put!')});
app.delete('/article', (req: Request, res: Response) => {res.send('Hello delete!')});
app.get('/article/list', (req: Request, res: Response) => {res.send('Hello article/list!')});
// ... 等等其他接口

Методы экспресс-маршрутизации являются производными от одного из методов HTTP, присоединенных к экземпляру экспресс-класса. Поддерживаются следующие методы маршрутизации, соответствующие методам HTTP: get, post, put, head, delete, options и т. д.

Коллега Робин посмотрел на код и спросил:Overnight-Learn-1.png

Поскольку создается все больше и больше интерфейсов, код неизбежно становится сложным и избыточным.Чтобы решить эту проблему, Лео представил Express'sRouter(), создаватьУстанавливаемые модульные обработчики маршрутов. Экземпляр маршрутизатораполное промежуточное ПОа такжесистема маршрутизации. Поэтому его часто называют «Микро приложение".

Лео Новый файлapp.router.ts, повторно реализуйте приведенный выше интерфейс:

// app.router.ts

import express, { Router, Request, Response } from 'express';
const router: Router = express.Router();

router.get('/', (req: Request, res: Response) => {res.send('Hello get!')});
router.post('/', (req: Request, res: Response) => {res.send('Hello post!')});
router.put('/', (req: Request, res: Response) => {res.send('Hello put!')});
router.delete('/', (req: Request, res: Response) => {res.send('Hello delete!')});
router.get('/user', (req: Request, res: Response) => {res.send('Hello api/user!')});

export default router;

Затем используйте его в app.ts, потому чтоexpress.Router() является промежуточным программным обеспечением, поэтому вы можете использоватьapp.use()использовать:

// app.ts

// 删除原来路由声明
import router from "../controller/app.router";
app.use('/api', router);

здесьapp.useпервый параметр/apiУказывает корневой путь этой группы объектов маршрутизации, второй параметрrouterПредставляет набор объектов маршрутизации.

Итак, реализован следующий интерфейс API:

  • /api
  • /api/user

Убедившись, что все интерфейсы работают нормально, Лео задумался, так как каждый маршрут Express маршрутизируется черезназвание маршрутаа такжеспособ обработки маршрутасостав, то почему нельзя добавить плагин к экспрессу? Добавьте декораторов к каждому маршруту для украшения. К счастью, уже есть большой парень, который реализовал этот плагин, и это сегодняшний главный герой——OvernightJS. Давайте вместе посмотрим на этот потрясающий OvernightJS.

2. Введение в базовые знания

Overnight-Learn-2.pngПрежде чем мы начнем знакомить с Overnight, давайте рассмотрим «Decorators» и «Reflect»:

1. Декоратор

1.1 Что такое декоратор?

В TypeScript декоратор — это специальный тип объявления, который может быть присоединен к объявлению класса, методу, методу доступа, свойству или параметру.По сути функция. Декораторы передаются нам в объявлениях и членах классов.синтаксис метапрограммированияДобавление выносок обеспечивает способ.

Несколько вещей, о которых следует помнить:

  • Декоратор — это объявление (выражение);
  • После выполнения выражениявернуть функцию;
  • Входные параметры функции:target,nameа такжеdescriptor;
  • После выполнения функции она может вернутьсяdescriptorобъект для настройкиtargetобъект;

Подробнее о декораторах читайте в документации.«Декораторы TypeScript».

1.2 Классификация декораторов

К декораторам обычно относятся:

  • декораторы класса;
  • Декораторы недвижимости;
  • декораторы методов;
  • Декораторы параметров;

1.3 Пример кода

Здесь мы берем декораторы класса в качестве примера, чтобы представить, как использовать декораторы:

function MyDecorators(target: Function): void {
  target.prototype.say = function (): void {
    console.log("Hello 前端自习课!");
  };
}

@MyDecorators
class LeoClass {
  constructor() {}
  say(){console.log("Hello Leo")}
}

let leo = new LeoClass();
leo.say(); 
// 'Hello Leo!';

1.4 Результаты компиляции

Декоратор на самом деле очень простой, после компиляции это просто функция, мы ее увидим дальше. Вот пример «1.3 Sample Code», чтобы увидеть результат его компиляции:

"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
function MyDecorators(target) {
    target.prototype.say = function () {
        console.log("Hello 前端自习课!");
    };
}
let LeoClass = class LeoClass {
    constructor() { }
    say() { console.log("Hello Leo"); }
};
LeoClass = __decorate([
    MyDecorators,
    __metadata("design:paramtypes", [])
], LeoClass);
let leo = new LeoClass();
leo.say();
// 'Hello Leo!';

На самом деле это__decorateФункция, вы можете поближе познакомиться с деталями ~ Как видно из скомпилированного кода JS,Декораторы выполняются при импорте модуля. следующим образом:

LeoClass = __decorate([
    MyDecorators,
    __metadata("design:paramtypes", [])
], LeoClass);

1.5 Резюме

Затем просмотрите знания декораторов по следующему рисунку.Decorator-Introduce.png

2. Reflect Metadata API

2.1 Что такое отражение?

Reflect (т.е. отражение) — новое дополнение к ES6.встроенные объекты, который предусматриваетперехват и манипулированиеAPI для объектов JavaScript. а такжеВсе свойства и методы Reflect статичны., как объект Math (Math.random() Ждать).

Для получения более подробной информации о Reflect, пожалуйста, прочитайте документацию"МДН Рефлект".

2.2 Почему появляется Reflect?

Его основная цель — сделать JS простым, чтобы нам не приходилось писать много кода Вот каштан 🌰, чтобы увидеть разницу между использованием Reflect и его неиспользованием: когда объект имеетSymbolКак обойти объектkeys?

const s = Symbol('foo');
const k = 'bar';
const o = { [s]: 1, [k]: 1 };

// 没有使用 Reflect
const keys = Object.getOwnPropertyNames(o).concat(Object.getOwnPropertySymbols(o));

// 使用 Reflect
Reflect.ownKeys(o);

Разве это не выглядит намного проще?

БолееReflectДля получения подробной информации, пожалуйста, прочитайте документацию"МДН Рефлект".

2.3 Что такое метаданные Reflect

Reflect Metadata — это предложение ES7, которое в основном используется дляДобавлять и читать метаданные при объявлении. TypeScript уже поддерживает его в версии 1.5+, вам просто нужно:

  • npm i reflect-metadata --save.
  • существуетtsconfig.jsonконфигурацияemitDecoratorMetadataопции.

Reflect Metadata может использоваться как декоратор и имеет два API:

  • использоватьReflect.metadata() API Добавить метаданные;
  • использоватьReflect.getMetadata() API читать метаданные.
@Reflect.metadata('inClass', 'A')
class LearnReflect {
  @Reflect.metadata('inMethod', 'B')
  public hello(): string {
    return 'hello world';
  }
}

console.log(Reflect.getMetadata('inClass', LearnReflect)); // 'A'
console.log(Reflect.getMetadata('inMethod', new LearnReflect(), 'hello')); // 'B'

КонечноReflectПредоставляет множество других API:

import 'reflect-metadata';

// 定义对象或属性的元数据
Reflect.defineMetadata(metadataKey, metadataValue, target);
Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

// 检查对象或属性的原型链上是否存在元数据键
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);

// 检查对象或属性是否存在自己的元数据键
let result = Reflect.hasMetadata(metadataKey, target);
let result = Reflect.hasMetadata(metadataKey, target, propertyKey);

// 获取对象或属性原型链上元数据键的元数据值
let result = Reflect.getMetadata(metadataKey, target);
let result = Reflect.getMetadata(metadataKey, target, propertyKey);

// 获取对象或属性的自己的元数据键的元数据值
let result = Reflect.getOwnMetadata(metadataKey, target);
let result = Reflect.getOwnMetadata(metadataKey, target, propertyKey);

// 获取对象或属性原型链上的所有元数据键
let result = Reflect.getMetadataKeys(target);
let result = Reflect.getMetadataKeys(target, propertyKey);

// 获取对象或属性的所有自己的元数据键
let result = Reflect.getOwnMetadataKeys(target);
let result = Reflect.getOwnMetadataKeys(target, propertyKey);

// 从对象或属性中删除元数据
let result = Reflect.deleteMetadata(metadataKey, target);
let result = Reflect.deleteMetadata(metadataKey, target, propertyKey);

// 通过装饰器将元数据应用于构造函数
@Reflect.metadata(metadataKey, metadataValue)
class C {
  // 通过装饰器将元数据应用于方法(属性)
  @Reflect.metadata(metadataKey, metadataValue)
  method() {
  }
}

Вам нужно не забыть настроить tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["es6", "dom"],
    "types": ["reflect-metadata"],
    "module": "commonjs",
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

В Overnight используются два основных API:

  • использоватьReflect.defineMetadata() API Добавить метаданные;
  • использоватьReflect.getOwnMetadata() API читать метаданные.

Ниже приведено введение в использование этих двух API с общим классом в Overnight:Reflect-Metadata-Use.png

2.4 Резюме

Вот обзор знаний Relect Metadata:Reflect-Metadata-Introduce.pngПоняв первые два пункта знаний, давайте начнем рассматривать Overnight.

3. Подробное объяснение ночевки

1. Введение концепции

OvernightJS** В основном обеспечивают поддержку декораторов TypeScript для экспресс-маршрутизации и управляют маршрутизацией с помощью декораторов**. Это немного абстрактно? Затем взгляните на следующий код:

@Controller('api/posts')
export class PostController {
    @Get(':id')
    private get(req: Request, res: Response) {
        // do something
    }
}

Как показано в приведенном выше коде, OvernightJS используется вот так, просто и понятно. Кроме того, OvernightJS предоставляет три библиотеки:

  • OvernightJS/core: основная библиотека;
  • OvernightJS/logger: библиотека инструментов ведения журнала;
  • НочевкаJS/jwt:JWTбиблиотека;

Далее я в основном представляю основную библиотеку OvernightJS/core.Если вам интересно, вы можете прочитать остальные две самостоятельно.На самом деле ядро ​​такое же.

2. OvernightJS/ядро, чтобы быстро приступить к работе

2.1 Установите OvernightJS/ядро

$ npm install --save @overnightjs/core express 
$ npm install --save-dev @types/express

2.2 Пример кода OvernightJS/ядра

Во-первых, давайте представим функции, которые необходимо реализовать в нашем примере кода:

  1. UserControllerкласс, ответственный за управлениеБизнес-логикаконтроллер;
  2. ServerControllerкласс, ответственный за управлениесервисная логикаконтроллер;
  3. Выполнить запуск службы;

первый шаг, импортируйте необходимые зависимости:

import { Controller, Get, Server } from '@overnightjs/core';
import { Request, Response } from 'express';
import * as bodyParser from 'body-parser';
const port = 3000;

второй шаг,выполнитьUserControllerДобрый:

@Controller('/users')
class UserController {
    @Get('/:id')
    private get(req: Request, res: Response) {
        return res.send(`hello, your id is:${req.params.id}`)
    }
    @Get('/list')
    private getList(req: Request, res: Response) {
        return res.send([
          {name: "leo", age: 17},
          {name: "robin", age: 19}
        ])
    }
}

в заявленииUserControllerclass, используйте OvernightJS/core's@Controllerдекоратор, использовать"/users"Путь используется в качестве параметра для указания адреса маршрутизации для текущего контроллера маршрутизации, который можно понимать как «корневой путь» этой группы маршрутов.Все пути интерфейса, реализованные в этом классе, будут основаны на «корневом пути». . затем вUserControllerкласс, предоставленный через OvernightJS/core@Getдекоратор, используется отдельно"/:id" а также"/list"Путь используется в качестве параметра для привязки маршрута.

наконецUserControllerАдреса маршрутизации, реализованные классом, включают:

  • /user/:id
  • /users/list

третий шаг,выполнитьServerControllerДобрый:

class ServerController extends Server {
    constructor() {
        super();
        this.app.use(bodyParser.json());
        super.addControllers(new UserController());
    }
    public start(port?: number): void {
        this.app.listen(port, () => {console.log('启动成功,端口号:',port)});
    }
}

ServerControllerКлассы наследуются от OvernightJS/coreServerкласс, вызывая конструкторsuper.addControllers(new UserController())Чтобы реализовать добавление ранее объявленного класса контроллера маршрутизации в массив контроллеров, управляемый OvernightJS/core. Также в этом классе мы также объявляемstartметод, используемый для запуска сервера.

четвертый шаг, чтобы реализовать логику запуска сервера:

const server = new ServerController();
server.start(port);

Запустить сервер здесь довольно просто~~

Весь процесс реализации примера кода выглядит следующим образом: Объявляются два класса:UserControllerа такжеServerController, соответственноконтроллер бизнес-логикиа такжеКонтроллер для сервисной логики, и, наконец, удалить экземпляр в основной записи и выполнить результат создания экземпляраstartспособ запуска службы.   Окончательный полный код выглядит следующим образом:

import { Controller, Get, Server } from '@overnightjs/core';
import { Request, Response } from 'express';
import * as bodyParser from 'body-parser';
const port = 3000;

@Controller('users')
class UserController {
    @Get(':id')
    private get(req: Request, res: Response) {
        return res.send(`hello, your id is:${req.params.id}`)
    }
    @Get('list')
    private get(req: Request, res: Response) {
        return res.send([
          {name: "leo", age: 17},
          {name: "robin", age: 19}
        ])
    }
}

class ServerController extends Server {
    constructor() {
        super();
        this.app.use(bodyParser.json());
        super.addControllers(new UserController());
    }
    public start(port?: number): void {
        this.app.listen(port, () => {console.log('启动成功,端口号:',port)});
    }
}

const server = new ServerController();
server.start(port);

 

3. Анализ OvernightJS/основного декоратора

В процессе чтения исходного кода я следил за всеми декораторами в OvernightJS/core согласноИзмерение структуры исходного каталогаРезультаты классификации следующие:Overnight-Decorators-Classify.pngИз приведенного выше рисунка ясно видно, что OvernightJS/core предоставляет нам четыре основных категории декораторов.Для конкретного использования, пожалуйста, также взгляните на официальную документацию веб-сайта~

4. Анализ архитектуры OvernightJS/ядра

Структура OvernightJS/core относительно проста и выглядит примерно так:Overnight-Design.pngВ OvernightJS/core предусмотрены две основные категории:Serverкласс иDecoratorsсопутствующие методы. вServerв классеaddConterllersМетод является ключевым и будет подробно рассмотрен в следующем разделе. Ха-ха

5. OvernightJS/ядро связано с Express

Оглядываясь назад на Express, мы часто пропускаемapp.use(path, route)чтобы определить интерфейс:

app.use(path, route);

Так что насчет OvernightJS? ? упоминалось в предыдущем разделеaddConterllersЧто это за метод? ?

На самом деле, OvernightJS, по сути, вызываетaddConterllers()метод для связи с Express. можно понимать какМост между OvernightJS и Express, который принимает в качестве параметра контроллер маршрутизации, определенный OvernightJS/core, и передаетuseметод, добавьте маршрут в Express и осуществите регистрацию экспресс-маршрута.

Давайте посмотрим на исходный кодaddControllersЧто делает метод:

// core/lib/Server.ts

public addControllers(
    controllers: Controller | Controller[],
    routerLib?: RouterLib,
    globalMiddleware?: RequestHandler,
): void {
    controllers = (controllers instanceof Array) ? controllers : [controllers];
    const routerLibrary: RouterLib = routerLib || Router;
    controllers.forEach((controller: Controller) => {
        if (controller) {
            const routerAndPath: IRouterAndPath | null = this.getRouter(routerLibrary, controller);
            if (routerAndPath) {
                if (globalMiddleware) {
                    this.app.use(routerAndPath.basePath, globalMiddleware, routerAndPath.router);
                } else {
                    this.app.use(routerAndPath.basePath, routerAndPath.router);
                }
            }
        }
    });
}

Давайте упростим приведенный выше код и сохраним исходный код основных функций:

public addControllers(
    controllers: Controller | Controller[],
    routerLib?: RouterLib,
    globalMiddleware?: RequestHandler,
): void {
  // ... 省略其他代码
    controllers = (controllers instanceof Array) ? controllers : [controllers];
    controllers.forEach((controller: Controller) => {
        this.app.use(routerAndPath.basePath, routerAndPath.router);
    });
}

Как видно из приведенного выше кода,addControllersМетод поддерживает передачу одного контроллера или массива контроллеров через методforEachперебирает каждый контроллер и помещаетpath а такжеrouterпередается как параметрapp.useметод, реализуйте регистрацию маршрута Express.

4. Ночной VS экспресс

Из предыдущего введения в концепцию мы знаем, что OvernightJS в основном обеспечивает поддержку декораторов TypeScript для экспресс-маршрутизации и управляет маршрутизацией через декораторы.

ТакВ чем разница между использованием OvernightJS и его неиспользованием?Ниже мы реализуем те же функции через OvernightJS и Express соответственно.Функции включают в себя: локальный запуск порта 4000, поддержкуapi/users/:idинтерфейс.

1. Реализация на ночьJS

Сначала реализуйте входной файл, где путем создания экземпляраServerControllerкласс и выполнить созданную структуруstartспособ запуска службы:

// customApp.ts

import ServerController from "../controller/custom.server.controller";
const port = 4000;

const server = new ServerController();
server.start(port);

Конфигурация tsconfig.json выглядит следующим образом:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}

Общий процесс аналогичен приведенному выше коду, а следующим шагом является начало реализации конкретногоServerController Добрый:

// controller/custom.server.controller.ts

import { Server } from "@overnightjs/core";
import RouterController from "./custom.router.controller";

class ServerController extends Server {
    constructor() {
        super();
        super.addControllers(new RouterController());
    }
    public start(port?: number): void {
        this.app.listen(port, () => {
            console.log('启动成功,端口号:',port)});
    }
}

export default ServerController;

  наконец понялRouterControllerКласс, методы маршрутизации в рамках API, определены в этом классе:

// controller/custom.router.controller.ts
import { Request, Response } from 'express';
import { Controller, Get, Put } from '@overnightjs/core';

@Controller("api/users")
class RouterController {
    @Get(":id")
    private get(req:Request, res:Response): any{
        res.send("hello leo!")
    }
}

export default RouterController;

 

2. Экспресс-внедрение

Как и раньше, вот первая реализация входного файла:

// app.ts

import ServerController from "../controller/server.controller";
const port = 4000;

const server = new ServerController();
server.start(port);

Затем реализуйте конкретныеServerController Добрый:

// controller/server.controller/.ts

import express, { Application } from 'express';
import RouterController from "./router.controller";

class ServerController {
    app: Application = express();
    constructor(){this.addControllers()};
    public addControllers(){
        const Router = new RouterController().getController();
        this.app.use('/api/users', Router);
    }
    public start(port?: number): void {
        this.app.listen(port, () => {console.log('启动成功,端口号:',port)});
    }
}

export default ServerController;

  наконец понялRouterController Добрый:

// controller/router.controller.ts

import express, { Router, Application, Request, Response, NextFunction } from "express";

class RouterController {
    router: Router = express.Router();
    constructor() { this.addControllers()};
    public getController = (): Router => this.router;
    public addControllers(): void {
        this.router.get("/:id", this.get);
    }
    public get (req: Request, res: Response, next: NextFunction){
        res.send("hello leo!")
        next();
    }
}

export default RouterController;

 

3. Сравнение двух

Я считаю, что друзья, которые видят здесь, имеют общее представление о первых двух методах реализации.Далее давайте посмотрим на разницу между двумя реализациями через картинку.Overnight-VS-Express.png

V. Резюме

Эта статья в основном знакомит с базовым использованием функций маршрутизации OvernightJS и Express, а затем использует их для достижения одной и той же функции маршрутизации и сравнивает преимущества OvernightJS.Друзьям, использующим Express + TypeScript, рекомендуется попробовать использовать OvernightJS~