Адрес исходного кода этого проекта: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 и т. д.
Коллега Робин посмотрел на код и спросил:
Поскольку создается все больше и больше интерфейсов, код неизбежно становится сложным и избыточным.Чтобы решить эту проблему, Лео представил 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, давайте рассмотрим «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 Резюме
Затем просмотрите знания декораторов по следующему рисунку.
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:
2.4 Резюме
Вот обзор знаний Relect Metadata:Поняв первые два пункта знаний, давайте начнем рассматривать 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/ядра
Во-первых, давайте представим функции, которые необходимо реализовать в нашем примере кода:
-
UserController
класс, ответственный за управлениеБизнес-логикаконтроллер; -
ServerController
класс, ответственный за управлениесервисная логикаконтроллер; - Выполнить запуск службы;
первый шаг, импортируйте необходимые зависимости:
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}
])
}
}
в заявленииUserController
class, используйте 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 согласноИзмерение структуры исходного каталогаРезультаты классификации следующие:Из приведенного выше рисунка ясно видно, что OvernightJS/core предоставляет нам четыре основных категории декораторов.Для конкретного использования, пожалуйста, также взгляните на официальную документацию веб-сайта~
4. Анализ архитектуры OvernightJS/ядра
Структура OvernightJS/core относительно проста и выглядит примерно так:В 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. Сравнение двух
Я считаю, что друзья, которые видят здесь, имеют общее представление о первых двух методах реализации.Далее давайте посмотрим на разницу между двумя реализациями через картинку.
V. Резюме
Эта статья в основном знакомит с базовым использованием функций маршрутизации OvernightJS и Express, а затем использует их для достижения одной и той же функции маршрутизации и сравнивает преимущества OvernightJS.Друзьям, использующим Express + TypeScript, рекомендуется попробовать использовать OvernightJS~