我爲 Express 開了外掛

圖片

本項目源碼地址:https://github.com/pingan8787/Leo-JavaScript/blob/master/Cute-Gist/LearnSource/OvernightDemo/html

隨着 Nodejs 在前端涉及領域愈來愈廣,也愈來愈成熟,相信不少朋友已經嘗試或使用過 Nodejs 開發服務端項目了。 前端

本文我將和你們一塊兒回顧 Express,而後介紹一個超級外掛——「OvernightJS」,它強大的地方在於,它將爲 Express 路由提供 TypeScript 裝飾器支持,使得咱們開發路由更加簡單,代碼複用性更好。 node

這裏也但願幫助你們對 TypeScript 的裝飾器有更深瞭解。git

接下來跟本文主角 Leo 一塊兒來看看這個外掛吧~es6

1、背景介紹

最近 Leo 打算使用 Express 來開始重構本身博客的服務端項目,通過認真研究和設計,並肯定完方案,Leo 開始下手啦:github

// 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 配置以下:express

{
  "compilerOptions": {
    "target""es6",
    "module""commonjs",
    "strict"true,
  "esModuleInterop"true,
    "experimentalDecorators"true, // 開啓裝飾器
    "emitDecoratorMetadata"true,  // 開啓元編程
    "skipLibCheck"true,
    "forceConsistentCasingInFileNames"true
  }
}

基本代碼寫完,測試能不能跑起來。 編程

Leo 在命令行使用 ts-node 命令行執行。(ts-node 用來直接運行 ts 文件,詳細介紹請查看文檔,這裏不細講咯):json

$ ts-node app.ts

看到命令行輸出:api

Example app listening on port 3000!

服務跑起來了,心情愉快。

接下來 Leo 使用 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!')});
// ... 等等其餘接口

Express 路由方法派生自 HTTP 方法之一,附加到 express 類的實例。 支持對應於 HTTP 方法的如下路由方法:get、post、put、head、delete、options等等。

同事 Robin 看了看代碼,問到:圖片

隨着接口越寫越多,代碼難免出現複雜和冗餘的狀況,爲了解決這個問題,Leo 引入 Express 的 Router() ,來建立「可安裝的模塊化路由處理程序」。Router 實例是「完整的中間件」「路由系統」。所以,經常將其稱爲「「微型應用程序」」。

Leo 新建文件 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

肯定全部接口正常運行後,Leo 琢磨着,既然 Express 每一個路由都是由「路由名稱」「路由處理方法」組成,那爲何不能給 Express 加個外掛?爲每一個路由添加裝飾器來裝飾。 

幸運的是,已經有大佬實現這個外掛了,它就是今天主角——OvernightJS。 

2、基礎知識介紹

圖片在開始介紹 Overnight 以前,咱們先回顧下「裝飾器」和「Reflect」:

1. 裝飾器

1.1 什麼是裝飾器?

TypeScript 中,裝飾器(Decorators)是一種特殊類型的聲明,它可以被附加到類聲明、方法、訪問符、屬性或參數上,「本質上仍是個函數」。 

裝飾器爲咱們在類的聲明及成員上經過「元編程語法」添加標註提供了一種方式。

須要記住這幾點:

  • 裝飾器是一個聲明(表達式);
  • 該表達式被執行後,「返回一個函數」
  • 函數的入參分別爲 targetname 和 descriptor
  • 執行該函數後,可能返回 descriptor 對象,用於配置 target對象;

更多裝飾器詳細介紹,請閱讀文檔《TypeScript 裝飾器》(https://www.tslang.cn/docs/handbook/decorators.html)。

1.2 裝飾器分類

裝飾器通常包括:

  • 類裝飾器(Class decorators);
  • 屬性裝飾器(Property decorators);
  • 方法裝飾器(Method decorators);
  • 參數裝飾器(Parameter decorators);

1.3 示例代碼

這裏以類裝飾器(Class decorators)爲例,介紹如何使用裝飾器:

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 示例代碼》爲例,看看它的編譯結果:

"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 ?

Reflect(即反射)是 ES6 新增的一個「內置對象」,它提供用來「攔截和操做」 JavaScript 對象的 API。而且 「Reflect 的全部屬性和方法都是靜態的」,就像 Math 對象( Math.random() 等)。

更多 Reflect 詳細介紹,請閱讀文檔《MDN Reflect》(https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect)。

2.2 爲何出現 Reflect?

其核心目的,是爲了保持 JS 的簡單,讓咱們能夠不用寫不少代碼,這裏舉個栗子

相關文章
相關標籤/搜索