# TYPESCRIPT
此筆記是根據這門課程所製作而成的
https://www.udemy.com/course/understanding-typescript/
> [name=Layors] from 2022/10/11
Typescript是JavaScript的超集
TypeScript並不能像JavaScript一樣被瀏覽器所執行,也無法被node.js執行
->TypeScript會把自己編譯回JavaScript檔
TypeScript追加了JavaScript沒有的型別
如果型別不對會報錯,但一樣會把js檔編譯出來
typeof可以用來確認某變數的型別,而這是js的東西
而且他在執行時才會作用,而非編譯時
## Basic & type
TS給予型別的方式:在變數後面加冒號再加型別
```typescript=
function add(n1: number, n2: number) {
return n1 + n2;
}
```
### core type
通通都寫小寫
在建立變數時,若已賦值則通常不會在變數後面加型別
如果只有宣告而未賦值,則可加入型別
- number
沒有分整數或浮點數,就是number
- string
可以用'', "", \`\`來包
- boolean
沒有truthy跟falsy
> [name=Layors] from 2022/10/12
- object
可以只標示object,告訴TS這是某個object
或是可以標示整個完整的object,告訴TS裡面有什麼property
```typescript=
const person: {
name: string;
age: number;
} = {
name: 'me',
age: 23
}
```
- Array
Array裡的型別可以鬆散也可以嚴謹
```typescript=
let activity: string[];//裡面只能存string
let activity: any[];//裡面可以存各種型別
```
- Tuple
固定長度、固定型別的Array
```typescript=
let lottery: [number, number];//裡面只能有兩個數字,不能少也不能多
lottery = [1, 2, 3];//會報錯
lottery = [1, 2];
lottery.push(3);//用push的話TS偵測不到錯誤
```
- Enums
enumerate 列舉
```typescript=
enum Role {
ADMIN,
READ_ONLY,
AUTHOR,
}
console.log(Role.ADMIN);//0
console.log(Role.READ_ONLY);//1
console.log(Role.AUTHOR);//2
```
數字可以自己更改
```typescript=
enum Role {
ADMIN = 5,
READ_ONLY, //6
AUTHOR, //7
}
...
enum Role {
ADMIN = 5,
READ_ONLY = 7,
AUTHOR = 9,
}
```
也可以不用數字,使用自訂的值
```typescript=
enum Role {
ADMIN = 'me',
READ_ONLY = 'you',
AUTHOR = 'him',
}
```
- any
不受限制
### 進階type
- union
讓一個變數可以接受多種型別
但有可能會報錯
所以可能會要搭配runtime check的typeof
```typescript=
function combine(input1: number | string, input2: number | string) {
let result;
if (typeof input1 === "number" && typeof input2 === "number") {
result = input1 + input2;
} else {
result = input1.toString() + input2.toString();
}
return result;
}
```
- literal type
特定形式的基本型別
```typescript=
//物種只能傳入cat或dog,而這兩個都是string,只是被限制只能用這兩個string
function animal(name: string, species: 'cat' | 'dog')
```
- type aliases/ custom type
自訂型別
```typescript=typescript=
type combinable = number | string; //可接受number跟string
type twoSpecies = 'cat' | 'dog'; //literal type
type User = {
name: string;
age: number;
}; //object,先定義好有助於減少不必要的重複
...
const user1: User = { name: 'Me', age: 23 };
```
- function return type
意思就是return的值的type
```typescript=
function add(n1: number, n2: number): number {
return n1 + n2;
}//return type加在function參數括號後面
function printIt(n1: number) {
console.log("It is " + n1);
}//沒有return任何東西,return type為void
```
假如我們console.log沒有return任何值的function,會得到undefined,同時undefined也是TS的其中一個型別
但要有return東西的function才能把return type設成undefined
所以沒有return任何東西的function要用void
- function
JS可以把function存在一個變數裡,為了怕他被其他不function的值存進去,所以有function type
```typescript=
let combine: Function;// F要大寫
combine = add;
combine = printIt;
```
但依然會被其他function蓋掉,所以可以更進一步的指定傳入的值、輸出的值的型別
```typescript=
let combine: (a: number, b: number) => number;// 用箭頭函式
```
- callback
```typescript=
function addAndPrint(n1: number, n2: number, cb: (num: number) => void)
//這裡的void代表這個cb不會對傳來的值做任何使用
//即使用到的cb有傳值過來也不會報錯
...
addAndPrint(10, 20, (num) => { //這裡的num不用加type是因為在上面已經定義過這裡的num會是什麼型別了
console.log(num);
});
```
- unknown
跟any很像,但unknown type的變數不可以被賦予給其他型別的變數
可以說是嚴格一點的any
可以用typeof來確認unknown變數的type,再把他做處理
- never
另一種return type
```typescript=
function newError(message: string, code: number): never {
throw { message: message, errorCode: code };
}
```
因為throw error,所以他永遠不會回傳東西,因此用never
## TypeScript Compiler
### 存檔後立即編譯
- 單一檔案
watch mode
```bash=
tsc app.ts --watch
tsc app.ts -w
```
進入這個模式可以讓你每次存檔TS檔之後就自動編譯
但只能對一個檔案使用
- 整個project
進到該project的目錄之後執行下面的程式碼
```bash=
tsc -init
```
執行完會建立tsconfig.json檔
此時輸入tsc便可編譯整個project裡的ts檔
此時可以搭配watch mode
```bash=
tsc --watch
tsc -w
```
> [name=Layors] from 2022/10/13
### setting
tsconfig.json告訴了typescript如何編譯ts檔
在最外面的大括號下面可以加上額外的設定
- exclude
用array來設定,可以用特定的檔案路徑,也可以選定特定的後綴
被選定的檔案或資料夾將不會被編譯
```json=
{
{
...
...
...
},
"exclude": [
"example.ts", //目前路徑除外的檔案
"testFolder", //除外的資料夾
"*.dev.ts", //目前路徑任何後綴為.dev.ts的檔案會被除外
"**/*.dev.ts", //任何資料夾底下的後綴為.dev.ts的檔案都會被除外
]
}
```
node_modules資料夾通常會被預設為除外,但在tsconfig.json裡不會被顯示出來
但如果你在tsconfig.json有加上exclude的設定,必須要把它放進去
不然typescript會變成不會把它除外
- include
如果加上這個設定,則變成只有在該array裡的檔案或資料夾才會被編譯
```json=
{
{
...
...
...
},
"include": [
"app.ts",
"testFolder"
]
}
```
- file
跟include很像,但他只能指定特定檔案,不能指定資料夾
```json=
{
{
...
...
...
},
"file": [
"app.ts",
]
}
```
### compilerOptions
這是在tsconfig.json裡的設定選項
- target
決定TS會被編譯成JS的哪個版本,ex:es5, es6, es2016....(es6為es2015)
- allowjs, checkjs
allowjs為true的話會讓js檔可以被ts編譯
checkjs為true的話js檔不會被ts編譯,但會檢查語法是否正常
- sourceMap
按F12並切換到source,可以看到我們的檔案,但只會看到js檔,而非ts檔
當sourceMap為true時,編譯後會產生app.js.map檔
在F12 source裡可以看到ts檔,也可在那裡設置斷點
- rootDir, outDir
一般來說,ts編譯完的js檔會被放在同一目錄下
而設置outDir可以指定編譯完的js檔該放在哪
而他也會複製資料夾結構
```json=
"outDir": "./jsFile",
```

但如上所示,outDir裡並沒有src資料夾
因為我們並沒有設置rootDir,因此他自動把src當根目錄
如果我們在外面又新增新的ts檔,他會重新選最外面的路徑為rootDir
因此一開始要設定好
```json=
"rootDir": "./",
```
rootDir必須包含所有的ts檔,不然會報錯
- removeComment
js檔裡將不會有註解
- noEmit, noEmitOnError
noEmit為true時不會產出js檔
noEmitOnError為true時在compile失敗時將不會產出js檔
- strict
在compilerOptions裡有一個Type Checking的區塊
如果strict設為true,則下面到alwaysStrict的選項就會被設為全開
如果沒有設為true,則要一個一個個別調整
- noUnusedLocal
若設為true,如果有未使用到的local變數,會報錯
- noImplicitReturns
若設為true,如果有function不一定會return值回來,會報錯
## class
### constructor
```typescript=
class Department {
name: string;
constructor(n: string) {
this.name = n;
}
}
const development = new Department("development");
```
### method
```typescript=
describe(){
console.log('Department: ' + this.name);
}
```
### access modifier
private, public, protected
public是預設的
我們不希望一些資料暴露給別人,因此會用private
private可以用在property跟method上
```typescript=
class Department {
name: string;
private employees: string[] = [];
constructor(n: string) {
this.name = n;
}
addEmployee(employee: string) {
this.employees.push(employee);
}
}
const development = new Department("development");
development.addEmployee("a");
development.addEmployee("b");
development.addEmployee("c");
empployees[0] = 'me'//若要嘗試修改private的東西會報錯
```
### shortcut for initialization
```typescript=
class person {
private name: string;
private age: number;
private job: string;
constructor(n: string, age: number, job: string) {
this.name = n;
this.age = age;
this.job = job;
}
}
```
太繁瑣了,因此有簡化的方式:
```typescript=
class person {
constructor(private name: string, private age: number, public job: string) {
}
}
```
在每個變數的前面加上access modifier,他就會自動幫你產生跟變數名字一樣的property
### readonly
如果你一些property不想之後被更改,可以在access modifier後面加上readonly
```typescript=
private readonly name: string;
```
### inheritance
```typescript=
class Department {
private employees: string[] = []
constructor(private id: string, private name: string) {
}
describe(){
console.log('Department: ' + this.name);
}
}
class ITDepartment extends Department {
constructor(id: string, private admins: string[]) {
super(id, 'IT');
}
}
```
在這裡,ITDepartment可以使用describe,因為他是public method
而他無法使用到employees,因為該property是private
使用private的話就只能在那個class裡使用,就算是繼承過來的也無法使用
此時可以改用protected,可以讓繼承的class用到該property的同時也保持不被外部更動
### getter, setter
```typescript=
class Department {
private employees: string[] = ['john', 'jane']
get employeeList(){
return this.employees;
}
set employeeList(name: string[]) {
this.employees = name;
}
constructor(private id: string, private name: string) {
}
}
const development = new Department("development");
console.log(development.employeeList)
development.employeeList = ["me"];
console.log(development.employeeList);
```
- getter
裡面一定要有return
當呼叫getter的時候後面不用加()
通常用在你想要對傳回去的資料做一些處理的時候
- setter
括號裡面一定要有參數
當呼叫setter時一樣不用加(),用類似把值指定給變數的方式使用
> [name=Layors] from 2022/10/14
### static method & property
不必新建一個該class的object就能從class裡呼叫
用法: 在method或property前面加上static
```typescript=
class Department {
//static property
static establishedAt: number = 2022;
//static method
static createEmployee(name: string){
return {name: name};
}
constructor(private id: string, private name: string) {
//不能用this,下面這段code會報錯
console.log(this.establishedAt);
//要用class的名字來呼叫
console.log(Department.establishedAt);
}
}
console.log(Department.establishedAt);
const newEmployee = Department.createEmployee("me");
```
在class裡面是不能用this來呼叫static property跟static method的
### abstract class
當被繼承的class的method, property沒辦法提供一個default,希望繼承你的class都強制要override他時,用abstact
此時class跟該method, property前面都要加上abstract,method後面不加上{},property不賦予初始值
```typescript=
abstract class Department {
abstract target: string;
constructor(private id: string, private name: string) {
}
abstract describe();
}
```
當class為抽象時,無法建立該class的object
### private constructor & singleton pattern
當你確定用這個class建立出來的object只會有一個的時候,會用到singleton pattern
用法為在constructor前面加上private,這樣做之後你就沒辦法用new建立該class的object
如何跟他互動?
->static method
```typescript=
class JohnDoe {
//用來儲存這個object
private static instance: JohnDoe;
private constructor() {
}
static getInstance() {
if (JohnDoe.instance) {
//回傳這個object
return this.instance;
} else {
// 若還沒建立則new一個JohnDoe,並回傳這個object
this.instance = new JohnDoe();
return this.instance;
}
}
}
//下面這段code會報錯
const John = new JohnDoe()
```
上面可以用this跟static property互動的原因是因為我們在static method裡
若在constructor裡一樣不能用this,要用class的名字來呼叫
以上面code為例,如果我們要呼叫JohnDoe,直接用John.getInstance()即可
## interface
interface用來形容一個object看起來應該要是怎麼樣
interface只存在於TypeScript,JavaScript沒有interface
interface不能給值給任何一個變數
```typescript=
interface Person {
name: string;
age: number;
talk(phrase: string): void;
}
```
但其實custom type可以做到一樣的事
```typescript=
type Person = {
name: string;
age: number;
talk(phrase: string): void;
}
```
interface只能用於形容一個object應該長怎樣,type則較為彈性
interface的重要點在於,他告訴了那個class一定要遵守他的規定
### class using interface
```typescript=
interface Talkable {
name: string;
talk(phrase: string): void;
}
class Person implements Talkable {
name: string;
age = 30; //可以多加interface裡沒形容的property
constructor(name: string) {
this.name = name;
}
talk(words: string) {
console.log(words);
}
}
```
- access modifier
interface裡不能使用private, public或protected
但可以使用readonly
在interface裡使用之後不用再去class裡再次宣告,一樣會有效果
- 多重interface
一個class可以使用多個interface
```typescript=
interface Named {
name: string;
}
interface Talkable {
talk(phrase: string): void;
}
class Person implements Named, Talkable {
name: string;
age = 30;
constructor(name: string) {
this.name = name;
}
talk(words: string) {
console.log(words);
}
}
```
- interface extends interface
```typescript=
interface Named {
name: string;
}
//現在如果有人要用這個interface的話,必須要有name跟talk()
interface Talkable extends Named{
talk(phrase: string): void;
}
```
而interface一樣可以extend多個interface
- optional parameters
interface可以讓某些property跟method變成不是一定要實做出來的
```typescript=
interface Named {
name: string;
age?: number;
greet?('words'): void;
}
```
使用的方法是加上問號
property在變數名稱後面加問號
method在變數名稱跟括號中間加問號
這個方法在class裡也可以使用
```typescript=
interface Named {
name: string;
age?: number;
greet?('words'): void;
}
class Person implements Named{
name: string;
age?: number;
constructor(name: string, job?: string) {
this.name = name;
}
}
```
- 補充:用interface做function
```typescript=
interface addFunction {
(a: number, b: number): number;
}
let add: addFunction;
add = (n1, n2) => {
return n1 + n2;
};
```
## Advanced types
### Intersection
把兩個custom type結合在一起
```typescript=
type Admin = {
name: string;
privileges: string[];
};
type Employee = {
name: string;
startDate: Date;
};
type elevatedEmployee = Admin & Employee;
const me: elevatedEmployee = {
name: "L",
privileges: ["create-server"],
startDate: new Date(),
};
```
基本上跟extends interfaces差不多
如果對兩個union type做intersection的話,會有跟使用and邏輯類似的結果
```typescript=
type a = string | boolean;
type b = number | string;
type c = a & b;//string
//補充
type d = string & number;//never
```
### Type Guard
基本上type guard就是各種確認型別的方法
- 基本型別
```typescript=
type combinable = number | string;
function add(n1: combinable, n2: combinable) {
if (typeof n1 === "string" || typeof n2 === "string") {
return n1.toString() + n2.toString();
}
//如果程式跑到這邊,那n1跟n2就一定都是number
return n1 + n2;
}
```
上面所寫的是基本型別的type guard,但如果遇到了自訂型別會發生一些狀況
- 自訂型別
```typescript=
type Admin = {
name: string;
privileges: string[];
};
type Employee = {
name: string;
startDate: Date;
};
type unknownEmployee = Admin | Employee;
function printEmployeeInfo(emp: unknownEmployee) {
console.log(emp.name);
if (typeof emp === "Admin") { //TS會報錯
console.log(emp.privileges);
}
}
```
因為程式碼最終要編譯回JS,而JS並不懂我們的自訂型別,因此無法用typeof來確認一個變數是否為自訂型別
因此,我們要用另一個方法:**in**
```typescript=
function printEmployeeInfo(emp: unknownEmployee) {
console.log(emp.name);
if ("privileges" in emp) { //這樣就會過了
console.log(emp.privileges);
}
}
```
- class
使用的是instanceof來確認你是哪個class
這是JS原生的功能
```typescript=
class car {
drive() {
console.log("driving...");
}
}
class truck {
drive() {
console.log("so heavy");
}
loadCargo() {
console.log("loading...");
}
}
type vehicle = car | truck;
const v1 = new car();
const v2 = new truck();
function useVehicle(vehicle: vehicle) {
vehicle.drive();
if (vehicle instanceof truck) {
vehicle.loadCargo();
}
}
```
但如果有用到interface,那這方法會失效,因為這是runtime check,而interface的部分不會被編譯成JS
### Discriminated Union
一種特殊的type guard,讓你對union type用type guard時可以簡單一些
有對class的type guard,但如果是用interface就無法使用
因此有一種方法,就是在所有的interface裡加上一樣的property
並且使用switch
```typescript=
interface Bird {
type: "bird";
flyingSpeed: number;
}
interface Horse {
type: "horse";
runningSpeed: number;
}
type Animal = Bird | Horse;
function moveAnimal(animal: Animal) {
let speed: number;
switch (animal.type) {
case "bird":
speed = animal.flyingSpeed;
break;
case "horse":
speed = animal.runningSpeed;
}
console.log("speed: " + speed);
}
```
> [name=Layors] from 2022/10/17
### Type Casting
type casting是指你自己幫助TypeScript去分辨他分辨不了的type
例如:操作DOM
```typescript=
//TypeScript不知道放在pass裡的東西到底是什麼,他只知道這是個HTML element
const pass = document.getElementById("pass")!;
```
因為TypeScript不知道你是不是會拿到東西,在這個情況下pass可能是null,在後面加!等於告訴TypeScript他抓到的東西永遠不會是null
有兩種解決方法
1. ```typescript=
const pass = <HTMLInputElement>document.getElementById("pass")!;
```
在前面加上<>,並在裡面寫明這是什麼東西
這個有點像是react的方法
2. ```typescript=
const pass = document.getElementById("pass")! as HTMLInputElement;
```
### Index
```typescript=
interface ErrorContainer{
email: string
[Prop: string]: string;
}
const err: ErrorContainer = {
email : 'invalid email',
name: 'invalid name'
}
```
上面的code意思是這個object的key一定要是string,而key裡存的值也是string
email: string的意思是你先預設了這個object裡一定會有一個叫email的key
之後在建立這個物件的時候就一定要寫出他,不然會報錯
### Function Overload
```typescript=
type combinable = number | string;
function add(n1: number, n2: number): number
function add(n1: combinable, n2: combinable) {
if (typeof n1 === "string" || typeof n2 === "string") {
return n1.toString() + n2.toString();
}
return n1 + n2;
}
```
根據變數的型別、數量不同,做出不同類型的return,但function名字都一樣
假如沒做function overload,使用add function的結果都會被TypeScript識別為combinable
而有做的話,可以被識別為各種型別
!!!但要注意,一旦做了function overload,下面function的implementation會變得無法呼叫,你只能去呼叫已經宣告的function overload
像上面的例子,如果我們把兩個string丟進add裡,TypeScript會報錯,因為你只有寫出當兩個變數都是number時會return什麼,還沒寫出丟兩個string進去會發生什麼
```typescript=
function add(n: number): number
function add(n1: number, n2: number): number
function add(n1: number, n2?: number) {
if(n2 !== undefined){
return n1 + n2;
}
return n1;
}
```
### optional chaining
當你在別的地方(ex:database)取得了一個object,但你不知道這個object裡的某個property是否存在,Typescript不會報錯,因為他偵測不到
這時可以用option chining
```typescript=
type JobData = {
title: string;
desc: string;
};
type Person = {
name: string;
age?: number;
job?: JobData;
};
const John: Person = {
name: "John",
// age: 23,
// job: { title: "paladin", desc: "May light blesses you" },
};
console.log(John?.age);
console.log(John?.job?.title);
```
先定義好取出來的資料的結構,可能會沒define到的property加上問號
之後要用到該property時,在該object的後面加上問號,如果該property不存在,不會產生runtime error而跳出程式
### nullsh coalescing
```typescript=
const input = "";
const stored = input || "default value";
console.log(stored);
```
如果你有一個input你不知道他是null, undefined或是有東西,用上面的程式可以讓你只在input有值的時候做操作,但如果是空字串或是數字0,這也會被歸類在falsy value
但如果我們想要保存空白input或數字0,要用另一種方式:??
```typescript=
const input = "";
const stored = input ?? "default value";
console.log(stored);
```
**NEXT**
> [TYPESCRIPT](/-u29b5iuS8GMMwrlaCirEQ)