---
###### tags: `TypeScript`
---
# 10/19
## 思考 - 如何將 Analyzer 變成單例模式
- 單例模式要求 Analyzer 不能被外部實例化
> 將 constructor() 設為 private,並設置屬性 private static instance: DellAnalyzer;
> 設置靜態方法 static getInstance() 給外部呼叫
> Crawler 方法不允許外部呼叫,設置 private
```javascript=
export default class DellAnalyzer implements Analyzer {
private static instance: DellAnalyzer;
static getInstance() {
if (!DellAnalyzer.instance) {
DellAnalyzer.instance = new DellAnalyzer();
}
return DellAnalyzer.instance;
}
/*
* .... 略
*/
private constructor() {}
}
// 將 new Analyzer 改寫
const analyzer = DellAnalyzer.getInstance();
new Crawler(url, analyzer);
```
> 設置一個屬性就是自己本身,
> 如果自己不存在-> new 一個自己
> 如果已存在 -> 回傳存在屬性內的自己
### 編譯 & 配置優化
- 設定 tsconfig.json 內 "outDir": "./build", 作為編譯路徑
- 安裝 nodemon
`npm install nodemon -D`
- 設定 data/* 目錄下的檔案不需要監聽
`"nodemonConfig": { "ignore": ["data/*"] },`
- 編譯指令 -w 代表持續監聽.ts文件
```javascript=
// package.json 配置
"scripts": {
"build": "tsc -w",
"start": "nodemon node ./build/crawler.js"
},
"nodemonConfig": {
"ignore": ["data/*"]
},
```
- 安裝 concurrenty 一次運行兩個命令 (感覺有點麻煩)
## tsconfig.json 配置介紹
- 編譯檔案組 ``"files": [
"11.ts"
],``
- 編譯 .js 目錄 `"outDir": "./build",`
- 刪除註解 `"removeComments": true,`
- 若型別是 any 必須寫出來 `"noImplicitAny": true, `
- 是否強制檢查 null `strictNullChecks`
- 編譯路徑 (測試不能用?) `"rootDir": "./src", `
- 是否允許 JS 被翻譯 ` "allowJs": true,`
- 是否產生 sourceMap 文件`"sourceMap": true,`
- 是否允許 未使用的變數存在`"noUnusedLocals": true,`
- 校驗函數參數`"noUnusedParameters": true,`
## 聯合類型、類型保護

>animal 是 Bird | Dog 的聯合類型,所以只會提示 fly 屬性
>為了解決聯合類型問題,使用類型保護
```javascript=
// 類型斷言的方式
function trainAnimal(animal: Bird | Dog) {
if (animal.fly) {
(animal as Bird).sing();
} else {
(animal as Dog).bark();
}
}
// in 語法來做類型保護
function trainAnimalSecond(animal: Bird | Dog) {
// 如果動物屬性有唱歌
if ("sing" in animal) {
animal.sing();
} else {
animal.bark();
}
}
// typeof 語法來做類型保護
function add(first: string | string, second: string | number) {
// 如果類型都是字串
if (typeof first === "string" || typeof second === "string") {
return `${first}${second}`;
}
return first + second;
}
```
```javascript=
// 使用 instanceof 語法來做類型保護 (只能用在 class)
class NumberObj {
count!: number;
}
function addSecond(first: object | NumberObj, second: object | NumberObj) {
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count;
}
return 0;
}
```
## 枚舉類型 Enum
```
# 初始化?
npm init -y
# 配置本地端 ts-node
npm install -D typescript
npm install -D ts-node
# or 全局安裝
npm install -g typescript
npm install -g ts-node
# 要安裝這個才能 console.log()
npm install -D tslib @types/node
```
- 狀態問題:不知道狀態 0 1 2 代表什麼意思
```javascript=
function getResult(status) {
if (status === 0) {
return "offline";
} else if (status === 1) {
return "online";
} else if (status === 2) {
return "deleted";
}
return "error";
}
```
- JS 寫法
```javascript=
const Status = {
OFFLINE: 0,
ONLINE: 1,
DELETED: 2,
};
function getResult(status: any) {
if (status === Status.OFFLINE) {
return "offline";
} else if (status === Status.ONLINE) {
return "online";
} else if (status === Status.DELETED) {
return "deleted";
}
return "error";
}
const result = getResult(Status.OFFLINE);
console.log(result);
```
- TS 中使用 枚舉類型 簡化
```javascript=
enum Status {
OFFLINE = 1, // 可以賦值設定,默認 0
ONLINE,
DELETED,
}
console.log(Status.OFFLINE); // 1
console.log(Status.ONLINE); // 2
console.log(Status.DELETED); // 3
console.log(Status[1]); // OFFLINE 也可以反向查 Status 內的值
```
## 泛型<T>
- 需求:若參數 first 為 string 則 second 也必須是 string
- 若參數 first 為 number 則 second 也必須是 number
```javascript=
function join(first: string | number, second: string | number) {
return `${first}${second}`;
}
```
- 泛型 generic 泛指的類型
- join 函式接收 T (任意) 類型的參數,那 first second 都必須要是 T 類型
```javascript=
function join<T>(first: T, second: T) {
return `${first}${second}`;
}
function map<T>(params: Array<T>) {
return params;
}
join<string>("1", "1");
map<string>(["test"]);
// 也可以傳遞多種類型
function otherJoin<T, P>(first: T, second: P) {
return `${first}${second}`;
}
otherJoin<string, number>("1", 1);
```
### 類中的泛型
- 需求 data 不僅可以傳 string[],也可以傳 number[]
```javascript=
class DataManager {
constructor(private data: string[] | number[]) {}
getItem(index: number): string | number {
return this.data[index];
}
}
const data = new DataManager(["1"]);
data.getItem(0);
```
- 使用基礎泛型
```javascript=
class DataManager<T> {
constructor(private data: T[]) {}
getItem(index: number): T {
return this.data[index];
}
}
const data = new DataManager<number>([1]);
data.getItem(0);
```
- 泛型繼承
```javascript=
interface Item {
name: string;
}
// 這個泛型未來會對應具體類型
// 我不知道傳過來的東西是什麼,但是一定有名字?所以傳進 function 的東西一定要有名字
class DataManager<T extends Item> {
constructor(private data: T[]) {}
getItem(index: number): string {
return this.data[index].name;
}
}
const data = new DataManager([{ name: "dell" }]);
data.getItem(0);
```
- 需求:希望T只能是 string | number
```javascript=
class DataManager<T extends string | number> {
constructor(private data: T[]) {}
getItem(index: number): T {
return this.data[index];
}
}
const data = new DataManager<string>([]);
```
- 如何使用泛型做 function 類型註解
```javascript=
function hello<T>(params: T) {
return params;
}
const func: <T>(param: T) => T = hello;
```
> 聽無