---
###### tags: `TypeScript`
---
# 10/22
## 裝飾器 Decorator ( @ )
### 參數裝飾器
- paramIndex 可以顯示參數位置
```typescript=
// 原型,方法名,參數所在位置
function paramDecorator(target: any, method: string, paramIndex: number) {
console.log(target, method, paramIndex);
//{ getInfo: [Function (anonymous)] } getInfo 0
}
class Test {
getInfo(@paramDecorator name: string, age: number) {
console.log(name, age);
}
}
const test = new Test();
test.getInfo("dell", 30); //dell 30
```
### 實際例子 - 異常捕獲
```typescript=
const userInfo: any = undefined;
class Test {
getName() {
try {
return userInfo.name;
} catch (e) {
console.log("userInfo.name 不存在");
}
}
getAge() {
try {
return userInfo.age;
} catch (e) {
console.log("userInfo.name 不存在");
}
}
}
const test = new Test();
test.getName(); //userInfo.name 不存在
```
==問題:每個方法都需要 try catch==
- 使用裝飾器
```typescript=
const userInfo: any = undefined;
function catchError(target: any, key: string,
descriptor: PropertyDescriptor) {
const fn = descriptor.value;
descriptor.value = function () {
try {
fn();
} catch (e) {
console.log("userInfo 存在問題");
}
};
}
class Test {
@catchError
getName() {
return userInfo.name;
}
@catchError
getAge() {
return userInfo.age;
}
}
const test = new Test();
test.getName(); // userInfo 存在問題
test.getAge(); // userInfo 存在問題
```
==問題:錯誤訊息不明確,不知道是哪個方法==
- 使用工廠模式
```typescript=
const userInfo: any = undefined;
// catchError 接收參數 回傳裝飾器
function catchError(msg: string) {
return function (target: any, key: string,
descriptor: PropertyDescriptor) {
const fn = descriptor.value;
descriptor.value = function () {
try {
fn();
} catch (e) {
console.log(msg);
}
};
};
}
class Test {
@catchError('userInfo.name 不存在')
getName() {
return userInfo.name;
}
@catchError('userInfo.age 不存在')
getAge() {
return userInfo.age;
}
}
const test = new Test();
test.getName(); // userInfo.name 不存在
test.getAge(); // userInfo.age 不存在
```
:::success
**達成異常捕獲的目的**
:::
## Express 改良 - 創建控制器、裝飾器
> 課程這段沒聲音
- 安裝 `npm i reflect-metadata --save`
- 在 tsconfig.json 打開這兩項才能用 @
```
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
```
- 創建 controller、將 router 內的邏輯進行遷移
```typescript=
import "reflect-metadata";
import { Request, Response } from "express";
import { controller, get } from "./decorator";
interface BodyRequest extends Request {
body: { [key: string]: string | undefined };
}
@controller
class LoginController {
@get("/logout")
logout() {req: BodyRequest, res: Response}{...}
@get("/")
home(req: BodyRequest, res: Response) {...}
```
- 創建 decorator 放置裝飾器
```typescript=
import { Router } from "express";
export const router = Router();
export function controller(target: any) {
for (let key in target.prototype) {
const path = Reflect.getMetadata("path", target.prototype, key);
const handler = target.prototype[key];
if (path) {
router.get(path, handler);
}
}
}
export function get(path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata("path", path, target, key);
};
}
```
### 多種請求方法生成
- 新增 /login
```typescript=
@post("/login")
login(req: BodyRequest, res: Response) {...}
// 新增 decorator 並改寫 @controller
enum Method {
get = "get",
post = "post",
}
export function controller(target: any) {
for (let key in target.prototype) {
const path = Reflect.getMetadata("path", target.prototype, key);
const method: Method = Reflect.getMetadata("method", target.prototype, key);
const handler = target.prototype[key];
if (path && method && handler) {
router[method](path, handler);
}
}
}
export function post(path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata("path", path, target, key);
Reflect.defineMetadata("method", "post", target, key);
};
}
```
- 新增 put
```typescript=
export function put(path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata("path", path, target, key);
Reflect.defineMetadata("method", "put", target, key);
};
}
```
:::info
問題:需求增加,邏輯重複
:::
:::success
解決:定義一個工廠,來生成這些東西
:::
```typescript=
// 工廠 產生 @
function getRequestDecorator(type: string) {
return function (path: string) {
return function (target: any, key: string) {
Reflect.defineMetadata("path", path, target, key);
Reflect.defineMetadata("method", type, target, key);
};
};
}
// 導出
export const get = getRequestDecorator("get");
export const post = getRequestDecorator("post");
export const put = getRequestDecorator("put");
```
### 新增 CrawlerController
- 因為登入後才能爬取、獲得資料,將功能獨立成一個 class
```typescript=
import fs from "fs";
import path from "path";
import "reflect-metadata";
import { Request, Response, NextFunction } from "express";
import { controller, get, use } from "./decorator";
import { getResponseData } from "../utils/util";
import Crawler from "../utils/crawler";
import Analyzer from "../utils/analyzer";
interface BodyRequest extends Request {
body: { [key: string]: string | undefined };
}
// 中間件
const checkLogin = (req: Request, res: Response, next: NextFunction) => {
const isLogin = req.session ? req.session.login : false;
if (isLogin) {
next();
} else {
res.json(getResponseData(null, "請先登入"));
}
};
@controller
class CrawlerController {
@get("/getData")
@use(checkLogin) // 中間件裝飾器
getData(req: BodyRequest, res: Response) {
const url = `http://www.dell-lee.com/`;
const analyzer = Analyzer.getInstance();
new Crawler(url, analyzer);
res.json(getResponseData(true));
}
@get("/showData")
@use(checkLogin)
showData(req: BodyRequest, res: Response) {
try {
const position = path.resolve(__dirname, "../../data/course.json");
const result = fs.readFileSync(position, "utf-8");
const data: string = JSON.parse(result);
res.json(getResponseData(data));
} catch (e) {
res.json(getResponseData(false, "數據不存在"));
}
}
}
```
- 遷移 get 路由後,==中間件 checkLogin 用裝飾器 @use(checkLogin)== 來撰寫
- 在 index.ts 引入 CrawlerController
```typescript=
// 中間件
export function use(middleware: RequestHandler) {
return function (target: any, key: string) {
Reflect.defineMetadata("middleware", middleware, target, key);
};
}
// controller 修改判斷
export function controller(target: any) {
for (let key in target.prototype) {
const path = Reflect.getMetadata("path", target.prototype, key);
const method: Method = Reflect.getMetadata("method", target.prototype, key);
const handler = target.prototype[key];
const middleware = Reflect.getMetadata("middleware", target.prototype, key);
if (path && method && handler) {
// 加入判斷中間件
if (middleware) {
router[method](path, middleware, handler);
} else {
router[method](path, handler);
}
}
}
}
```
- 加入 showData
```javascript=
import fs from "fs";
import path from "path";
@get("/showData")
@use(checkLogin)
showData(req: BodyRequest, res: Response) {
try {
// 記得修改路徑
const position = path.resolve(__dirname, "../../data/course.json");
const result = fs.readFileSync(position, "utf-8");
const data: string = JSON.parse(result);
res.json(getResponseData(data));
} catch (e) {
res.json(getResponseData(false, "數據不存在"));
}
}
```