---
###### tags: `TypeScript`
---
# 10/20
## 命名空間 namespace
- 創建目錄結構

- 以 class 的方式建立 Header、Content、Footer
```javascript=
class Header {
constructor() {
const elem = document.createElement("div");
elem.innerHTML = "This is Header";
document.body.appendChild(elem);
}
}
class Content {
constructor() {
const elem = document.createElement("div");
elem.innerHTML = "This is Content";
document.body.appendChild(elem);
}
}
class Footer {
constructor() {
const elem = document.createElement("div");
elem.innerHTML = "This is Footer";
document.body.appendChild(elem);
}
}
```
- 建立 Page
```javascript=
class Page {
constructor() {
new Header();
new Content();
new Footer();
}
}
// Html
<script>
new Page();
</script>
```
- 產生問題:**過多全局變數** (編譯後都是 var)

- 使用 namespace 模組化
```javascript=
namespace Home {
class Header {...}
class Content {...}
class Footer {...}
// 只將 Page 匯出
export class Page {...}
}
// Html
<script>
new Home.Page();
</script>
```
==透過這種方式,外部只能調用 Page==
### 模組化
- 創建 components.ts 將組件移入並 export
```javascript=
namespace Components {
export Header {...}
export Content {...}
export Footer {...}
}
```
- 修改 tsconfig.json 將所有檔案編譯成一個 .ts 文件
```
// 編譯成 page.js
"outFile": "./dist/page.js",
"module": "amd",
```
- 在 page.ts 中標示組件來源
```javascript=
///<reference path='./components.ts' />
```
- 也可以匯出介面 or 子命名空間
```javascript=
namespace Components {
export namespace SubComponents {
export class Test {}
}
export interface User {
name: string;
}
}
```
## 使用 import 語法模組化
- 改寫 page.ts
```javascript=
import { Header, Content, Footer } from "./components";
export default class Page {
constructor() {
new Header();
new Content();
new Footer();
}
}
// components.ts 模塊匯出
export class Header {...}
export class Content {...}
export class Footer {...}
// 引用 require.js 並改寫
<!-- require.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script>
require(['page'], (page) => {
new page.default()
})
</script>
```
## Parcel 打包工具
- 類似 webpack 打包工具 [連結](https://zh-tw.parceljs.org/getting_started.html)
`npm install parcel-bundler --save-dev`
### 類型定義文件
- 功能:幫助 TS 識別 JS 文件
- 撰寫 jquery.d.ts 讓 TS 識別 jQuery
```javascript=
// JQ 代碼
$(function () {
alert(123);
});
// 定義全局變量
declare var $: (param: () => void) => void;
// or 定義全局函數
declare function $(param: () => void): void;
// JQ 代碼
$(function () {
$("body").html("<div>123</div>");
});
declare function $(param: string): {
html: (html: string) => {};
};
```
- 函數重載機制:ex $可以定義多種使用方式
- 定義介面 JqueryInstance 優化 提高可讀性
```javascript=
interface JqueryInstance {
html: (html: string) => JqueryInstance;
}
declare function $(readyFunc: () => void): void;
declare function $(selector: string): JqueryInstance;
```
- 使用 interface 的語法,實現函數重載
```javascript=
// 定義全局函數
interface JqueryInstance {
html: (html: string) => JqueryInstance;
}
interface JQuery {
(readyFunc: () => void): void;
(selector: string): JqueryInstance;
}
declare var $: JQuery;
```
- 若使用 npm 安裝 jQuery
```javascript=
// 使用 import 方式載入
import $ from 'jquery'
// ES6 模塊化
declare module "jquery" {
interface JqueryInstance {
html: (html: string) => JqueryInstance;
}
// 混合類型
function $(readyFunc: () => void): void;
function $(selector: string): JqueryInstance;
namespace $ {
namespace fn {
class init {}
}
}
export = $;
}
```
## 泛型 keyof 語法
- js 中的 Object.keys()
- ==Object.keys() 回傳一個陣列,陣列中的各元素為直屬於 obj ,對應可列舉屬性名的字串。==
```javascript=
var arr = ['a', 'b', 'c'];
console.log(Object.keys(arr)); // console: ['0', '1', '2']
```
- keyof
```javascript=
interface Person {
name: string;
age: number;
gender: string;
}
getInfo<T extends keyof Person>(key: T): Person[T]
// 第一次相當於
type T = 'name';
key: "name";
Person["name"];
// 第二次
type T = 'age';
key: "age";
Person["age"];
// 第三次
type T = 'gender';
key: "gender";
Person["gender"];
```
- 完整
```javascript=
interface Person {
name: string;
age: number;
gender: string;
}
class Teacher {
constructor(private info: Person) {}
getInfo<T extends keyof Person>(key: T): Person[T] {
return this.info[key];
}
}
const teacher = new Teacher({
name: "dell",
age: 18,
gender: "male",
});
const test = teacher.getInfo("age");
console.log(test);
```
## 使用 Express 結合爬蟲
- 安裝 `npm install express --save`
- @types/express `npm i --save-dev @types/express`
- 創建 index.ts、router
- APP
```javascript=
import express from "express";
import router from "./router";
const app = express();
app.use(router);
// 聽 http://localhost:7001
app.listen(7001, () => {
console.log("伺服器啟動中...");
});
```
- Router
```javascript=
import { Router, Request, Response } from "express";
import Crawler from "./crawler";
import DellAnalyzer from "./dellAnalyzer";
const router = Router();
router.get("/", (req: Request, res: Response) => {
res.send("hello world");
});
router.get("/getData", (req: Request, res: Response) => {
// 執行爬蟲
const url = `http://www.dell-lee.com/`;
const analyzer = DellAnalyzer.getInstance();
new Crawler(url, analyzer);
res.send("getData Success!");
});
export default router;
```
- 設置登入權限
```javascript=
// 將預設路由顯示表單
router.get("/", (req: Request, res: Response) => {
res.send(`
<html>
<body>
<form method="post" action="/getData">
<input type="password" name="password"/>
<button>提交</button>
</form>
</body>
</html>
`);
});
// 修改為 POST 並設置密碼
router.post("/getData", (req: Request, res: Response) => {
if (req.body.password === "123") {
const url = `http://www.dell-lee.com/`;
const analyzer = DellAnalyzer.getInstance();
new Crawler(url, analyzer);
res.send("爬蟲 成功!");
} else {
res.send("密碼錯誤");
}
});
```
- 需使用中間件 **body-parser** 才不會報錯
```javascript=
app.use(bodyParser.urlencoded({ extended: false }));
```
:::warning
產生問題
1. express 庫的類型定義文件 .d.ts 文件類型描述不準確
2. 當我使用中間件的時候,對 req 或者 res 做了修改後,實際上類型並不能改變
:::
---
- 若修改node 內 Express 類型定義文件 ReqBody
```
ReqBody = {password:string|undefined},
```
==可以解決、但沒有意義,重新安裝後還 any==
- 應另寫 介面去繼承 Request
```javascript=
interface RequestWithBody extends Request {
body: {
password: string | undefined;
};
}
router.post("/getData", (req: RequestWithBody, res: Response) => {...}
```
- 修改成 key 的寫法後,未來任意增加屬性,類型都會是 string | undeundefined
```javascript=
interface RequestWithBody extends Request {
body: {
[key: string]: string | undefined;
};
}
```
- index.js 增加中間件
```javascript=
app.use((req: Request, res: Response, next: NextFunction) => {
req.teacherName = "dell";
next();
});
```
- 類型融合 > 創建 custom.d.ts
```javascript=
declare namespace Express {
interface Request {
teacherName: string;
}
}
// 可在 router 使用 teacherName
res.send(`${req.teacherName}密碼錯誤`);
```
:::success
透過 TS 擴展(??) 解決 1.類型描述不準確 2.中間件類型無法擴展
:::