###### tags: `Typescript`
# Typescript tutorial
[youtube教學](https://www.youtube.com/watch?v=2pZmKW9-I_k&list=PL4cUxeGkcC9gUgr39Q_yD6v-bSyMwKPUI)
## Compiling TypeScript
- `tsc filename.ts`: 將ts檔 Compiling to TypeScript, 每次修改完code都要執行一次才會重新 Compiling
- `tsc src/filename.ts`: 如果檔案不在目前terminal所在路徑, 可以再加src路徑, 作用同上
- `tsc filename.ts -w`: 修改完code 會即時將ts檔 Compiling to TypeScript, 可console看即時更新結果
## Type Basics 2~6
```typescript=
//string, number, boolean
let name:string;
let age: number;
let isOpen: boolean;
//array
let arr1: string[] = []; //array內value只能是string
let arr2: number[] = []; //array內value只能是number
let arr3: any[] = []; //array內value可以是任意型別
//union types
let mixed: (string|number|boolean)[] = []
let uuid: string|number;
uuid = 'emma';
uuid = 123;
//object
//任意物件內容都可以
let obj: object;
obj = {name:'emma', age:123};
//限定物件key與value型別
let objDetail:{
name:string,
age: number,
isDrive: boolean,
};
objDetail = {
name: 'emma',
age: 123,
isDrive: false,
};
// 物件內value型別可以任意
let obj = {name: any, age: any};
obj = {
name: 'emma',
age: 123
};
// 任意型別
let age: any = 25;
age = true;
```
## Better Workflow & tsconfig 7, Modules 14
- terminal cd 到 src file
- `tsc --init`在src資料夾會出現 tscconfig.json
```javascript=
// tscconfig.json
{
"compilerOption": {
"target": "es6", //指定編譯生成的JS版本
"module": "commonjs", //指定生成哪種模組 ex:es2015
"rootDir": "./src" //.ts檔案所在位置
"outDir": "./public": // .ts要compiler放到哪裡
"forceConsistentCasingInFileNames": true, //強制區分大小寫, 默認為false
"files": ["hello.ts"], //屬性指定要編譯的 TS 檔案
"include": ["src"]或["src/**/*.ts"], //屬性設定編譯時包含哪些檔案或資料夾, 路徑可調整
"exclude": ["node_modules"], //屬性設定編譯時排除哪些檔案或資料夾
},
}
// *-表示匹配0至多個字元(不包含分隔符號)
// ?-匹配一個相符字元(不包含分隔符號)
// **/-表示匹配所有子資料夾
//ps:以下code可以過, 但如果forceConsistentCasingInFileNames為true時,
會報錯誤:'2a' !== '2A'
//2a.ts
export const PI = 3.1415926;
//1a.ts
import PI from './2A.ts';
function fun(){
return PI;
}
```
- `tsc -w` 在vscode的filename.ts檔案, 下這個指令`tsc -w` 開啟同步compiler, 當改code的時候, 就會同步輸出js檔, 只要一更改ts就會馬上改js
[tscconfig.json設定參考](https://github.com/hstarorg/HstarDoc/blob/master/%E5%89%8D%E7%AB%AF%E7%9B%B8%E5%85%B3/TypeScript%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6tsconfig%E7%AE%80%E6%9E%90.md)
## Function Basics 8
```typescript=
//宣告型別為 Function
let greet: Function;
greet = () => {
console.log('hello world');
}
//宣告function的參數型別
//有參數c時宣告型別, 並且有 defaultValue 10
const add = (a:number, b:number, c:number | string = 10) => {
console.log(a+b);
console.log(c); //如果沒有= defaultValue 10, log結果為 undefind
}
add(5,10, 'emma');
//有參數c時宣告型別, 且c不能=預設 defaultValue 10
const add = (a:number, b:number, c?:number | string) => {
console.log(a+b);
console.log(c); //如果沒有= defaultValue 10, log結果為 undefind
}
add(5,10, 'emma');
//定義function沒有回傳value: void
const plusFn = (a:number, b: number, c?:number):void =>{
console.log(a+b);
console.log(c);
}
const plusFn = (a:number, b: number, c?:number):number =>{
return a+b+c;
}
```
## Type Aliases 9
```typescript=
//用type宣告型別
type StringOrNum = string|number;
type ObjWithName = {name:string, uid:StringOrNum};
const loginFn = (uid:StringOrNum, name:string) => {
console.log(`${name} has a uid is ${uid}`);
}
const helloFn = (user:ObjWithName) => {
console.log(`${user.name} say hello!`);
}
```
## Function Signatures 10, 宣告function三種例子, 參數與回傳值型別的差異
- 宣告function型別:
```typescript=
let greet: Function;
//example1: 宣告greet變數有兩個參數, 並定義型別, 與回傳值型別
let greet: (a:string, b:string) => void;
greet = (name:string, greeting:string):void => {
console.log(`${name} says ${greeting}`);
}
// example2: 如果有宣告回傳值則邏輯結束一定要回傳符合型別的value
let cal: (a:number, b:number, action:string) => number; //回傳number
cal= (a:number, b:number, action:string) => {
//if 沒有else會報錯, 一定要回傳number
if(action === 'add') {
return a+b
}else {
return a-b
}
}
// example3: 參數是物件
let loginFn: (obj: {name: string, age:number}) => void;
type UserType = {name: string, age:number};
loginFn = (user:UserType) => {
console.log(`${user.name} is ${user.age} years old.`);
}
```
## The DOM & Type Casting 11: 宣告DOM的型別有哪些
```typescript=
//example1: HTMLFormElement or HTMLAllCollection....等會有提示, 如下:
//<form> id="type"
const form = document.querySelect('.main') as HTMLFormElement;
console.log(form.children);
//example2:
//<select> id="type"
const type = document.querySelect('#type') as HTMLSelectElement;
//<input> id="type"
const type = document.querySelect('#type') as HTMLInputElement;
//<form> id="type"
const type = document.querySelect('#type') as HTMLFormElement;
```
## Classes 12, Public, Private & Readonly 13
```typescript=
class Invoice {
readonly client:string; //不管class內外都只能讀, 不能重新assign value
private detail:string; //只能在class裡面access和賦值
public amount:number; //class外也可以access和賦值
constructor(c: string, d: string, a:number){
this.client = c;
this.detail = d;
this.amount = a;
}
//另一種寫法
constructor(
readonly client:string;
private detail:string;
public amount:number;
){}
format(){
// this.client = 'something else' // error, 因為client是readonly不能重新賦值
return `${this.client} owes ${this.detail} for ${this.amount}`
}
}
const invOne = new Invoice('mario', 'work on the mario website', 250);
const invTwo = new Invoice('luigi', 'work on the mario website', 300);
//class就是一種型別
let invoices: Invoice[] = []; //class定義型別
invoices.push(invOne);
invoices.push(invTwo);
invoices.forEach(inv =>{
//因為inv.detail是private所以不能在class外存取, 但可從inv.format()取到detail的值, 因為format()是在class裡面access detail
console.log(inv.client, inv.detail, inv.amount, inv.format());
})
```
## Interfaces 15
```typescript=
//interface定義型別, 名稱要大寫開頭
interface User {
name: string;
age: number;
speak(a: string): void;
spend(b: number): number;
}
// 宣告userA的型別為User
const userA: User = {
name: 'emma',
age: 123,
speak(text: string):void {
console.log(text);
},
spend(amount: number): number {
console.log(amount);
return amount;
}
}
//用Interfaces宣告function argument 型別範例
const userFn = (users:User) => {
console.log('hello', person.name);
}
userFn(userA); //執行userFn
```
## Interfaces with Classes 16: class如何使用Interfaces
```typescript=
// src/interfaces/HasFormatter.ts
// 定義format回傳字串
export interfaces HasFormatter {
format(): string;
}
// src/classes/Invoice.ts
import { HasFormatter } from '../interfaces/HasFormatter.ts';
export class Invoice implements HasFormatter {
constructor(
readonly client:string; //不管class內外都只能讀, 不能重新assign value
private detail:string; //只能在class裡面access和賦值
public amount:number; //class外也可以access和賦值
){}
format(){
// this.client = 'something else' // error, 因為client是readonly不能重新賦值
return `${this.client} owes ${this.detail} for ${this.amount}`
}
}
```
## Rendering an HTML Template 17
- 參照react寫法即可
## Generics(泛型) 18
- Generics(泛型)很重要的一點,就是讓我們寫的方法可以適用在不同的型別,而非只能使用在單一型別。
- 定義泛型
```typescript=
// type
type Dict<T> = {
value: T;
};
// interface
interface WrappedValue<T> {
value: T;
}
// arrow function
const identity = <T>(x: T): T => x;
// function
function identity<T>(x: T): T {
return x;
}
```
- 範例如下:可以dynamic defind data type也可以重複使用uid, name, errorCode
```typescript=
interface Resource<T> {
data: T; //can dynamic defind
uid: number; //repeact use
name: string; //repeact use
errorCode: number; //repeact use
}
const docObj:Resource<object> = {
data: {name: 'emma'};
uid: number;
name: string;
errorCode: number;
}
const docStrArr: Resource<string[]> ={
data: ['emma', 'mark', 'lisa'];
uid: number;
name: string;
errorCode: number;
}
```

[Generic範例: type可以重複使用就看傳入的type是什麼1](https://github.com/total-typescript/typescript-generics-workshop/blob/main/src/01-dry-interfaces.solution.1.ts)
[Generic範例: type可以重複使用就看傳入的type是什麼2](https://github.com/total-typescript/typescript-generics-workshop/blob/main/src/01-dry-interfaces.problem.ts)
## 帶有限制的泛型 Type Parameter with Constraints:透過 `extends` 的使用,可以建立帶有**「限制」**的泛型:
```typescript=
interface WrappedValue<T extends string> {
value: T;
}
// ⭕️ T 滿足 string 的型別
const val: WrappedValue<'Aaron' | 'PJ'> = {
value: 'Aaron',
};
// ❌ T 不滿足 string 時會噴錯
// Type 'number' does not satisfy the constraint 'string'.
const val: WrappedValue<number> = {
value: 30,
};
// ❌ 因為沒有給 T 預設值,所以不能留空
// Generic type 'WrappedValue<T>' requires 1 type argument(s).
const val: WrappedValue = {
value: 30,
};
```
- 實際的例子像這樣:
```typescript=
// 接收 array: T[] 做為參數,回傳 Dictionary {[k: string]: T}
// 若把 T 的限制 { id: number } 拿掉的話,將會沒辦法確定 T 是帶有 id 的物件
const arrayToDict = <T extends { id: number }>(array: T[]): { [k: string]: T } => {
const out: { [k: string]: T } = {};
array.forEach((val) => {
out[val.id] = val;
});
return out;
};
```
## 帶有預設值的泛型 Generic parameter defaults
- 可以給泛型預設值,舉例來說,下面的程式碼指的是:沒有給 T 的話,T 預設的型別會是 string
```typescript=
interface WrappedValue<T = string> {
value: T;
}
```
- 但如果只是定義預設值的話,使用者可以任意更改該型別,例如:
```typescript=
// 預設 WrappedValue 中使用的型別是 string,但使用者可以改成自己想要使用的
const val: WrappedValue<number> = {
value: 30,
};
```
- 這時候,可以透過 extends 限制使用者給定的泛型,例如:
- 如果沒有提供 T 則預設該型別是 string
- 如果有給 T
- T 需要滿足 string,否則會噴錯
- 當 T 滿足 string 時,T 會變成變成實際代表的型別
```javascript=
interface WrappedValue<T extends string = string> {
value: T;
}
// 如果沒有提供 `T` 則預設該型別是 `string`
const val: WrappedValue = {
value: 'hello',
};
// ❌ 如果有給 T,但 T 不滿足 string 時,會噴錯
// Type 'number' does not satisfy the constraint 'string'.
const val: WrappedValue<number> = {
value: 30,
};
// ⭕️ 如果有給 T,當 T 滿足 string 時,T 會變成變成實際代表的型別
const val: WrappedValue<'Aaron' | 'PJ'> = {
value: 'Aaron',
};
```
[pjchender: generic進階用法](https://pjchender.dev/typescript/ts-generics/)
## Enums 19
- 基本跟在function component外宣告const物件類似
- 差異是 enums 是常數希望不能被更改, 提高程式可讀性
- 範例: value可以是數字or字串
```typescript=
enum requestStatusCodes {
error = 400,
success = 200,
}
enum requestWrongCodes {
missingParameter = 'A',
wrongParameterType = 'B',
invalidToken = 'C',
}
```
[TypeScript | 善用 Enum 提高程式的可讀性 - 基本用法 feat. JavaScript](https://medium.com/enjoy-life-enjoy-coding/typescript-%E5%96%84%E7%94%A8-enum-%E6%8F%90%E9%AB%98%E7%A8%8B%E5%BC%8F%E7%9A%84%E5%8F%AF%E8%AE%80%E6%80%A7-%E5%9F%BA%E6%9C%AC%E7%94%A8%E6%B3%95-feat-javascript-b20d6bbbfe00)
## Tuples 20
```typescript=
let values: [string, string, number]; // defind type
values = [obj.name, obj.address, obj.age]; //declare value
newValues = [...]; // 解壓縮前要defind type才不會報錯
```
## 斷言 as unknown
- 通常api拿回來的資料ts無法事先知道, 因此可以用as斷言人工定義會來的資料會是什麼類型
```javascript=
type Data ={
userId: number,
id: number,
title: string,
completed: boolean,
}
async function getData() {
const res = await fetch('https://jsonplaceholder.typicode.com/todo/1');
const data = await res.json() as Data;
}
```
## Record<key, value>
- 有時候只知道key跟value的型別
```javascript=
interface CatInfo {
age: number;
breed: string;
}
type CatName = "miffy" | "boris" | "mordred";
const cats: Record<CatName, CatInfo> = {
miffy: { age: 10, breed: "Persian" },
boris: { age: 5, breed: "Maine Coon" },
mordred: { age: 16, breed: "British Shorthair" },
};
```
## Pick<Type, Keys>
- 只需要某個type裡面某幾個項目, **篩選出某幾項**
```javascript=
interface Todo {
title: string;
description: string;
completed: boolean;
}
type TodoPreview = Pick<Todo, "title" | "completed">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
};
```
## Omit<Type, Keys>
- 省略掉某個type的某幾項, **省略掉某幾項**
```javascript=
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
type TodoPreview = Omit<Todo, "description">;
const todo: TodoPreview = {
title: "Clean room",
completed: false,
createdAt: 1615544252770,
};
type TodoInfo = Omit<Todo, "completed" | "createdAt">;
const todoInfo: TodoInfo = {
title: "Pick up kids",
description: "Kindergarten closes at 5pm",
};
```
## react component參數寫法
```javascript=
type UsersDataType = {
data: {
total: number;
users: UserType[];
factories: GroupType[];
departments: GroupType[];
groups: GroupType[];
};
}
type PropType = {
data: UsersDataType;
updateTable: Function;
};
//用在function component的參數方法
const App: React.FC<PropType> = ({data, updateTable}) => {
return<div>首頁</div>
}
```
## Wrap Up 21略