[TS] TypeScript Basic Type
此篇為各筆記之整理,非原創內容,資料來源可見下方連結與文後參考資料。
- Basic Types @ TypeScript
- TypeScript and Set Theory
- Top Types:指的是包含所有可能的型別,全集合,即
unknown
。 - Bottom Types:指的是不能接受任何值的型別,空集合,即
never
。
Array and Tuple
- The Array Type @ TypeScript > Object Types
- The ReadonlyArray Type @ TypeScript > Object Types
- Tuple Types @ TypeScript > Object Types
- readonly Tuple Types @ TypeScript > Object Types
Array and Readonly Array
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3]; // 等同於上面的寫法
let list: readonly number[] = [1, 2, 3]; // 不能修改 Array 裡的元素
let list: ReadonlyArray<number> = [1, 2, 3]; // 等同於上面的寫法
Tuple
Tuple 則是有固定長度和元素型別的陣列:
let tuple: [number, string, boolean, number] = [3, 'foo', true, 10];
tuple = [4, 'bar', false, 30];
實際上 Tuple 更像是以 number
作為 key 的物件型別,並且多了 length
屬性,例如:
interface StringNumberPair {
length: 4;
0: number;
1: string;
2: boolean;
3: number;
// other array methods...
}
Tuples with Label(帶有標籤的 tuple)
滑鼠移到該 type 上時,會顯示每個元素的 label
type PersonInfo = [name: string, country: string, age: number];
const aaron: PersonInfo = ['Aaron', 'Taiwan', 32];
Readonly Tuple
如果要把陣列直接當成 readonly tuple 使用,可以在定義陣列時加上 as const
,如此它就是會是一個值固定的 readonly tuple,如果沒加上 as const
的話,則會是 string[]
:
const phoneBrand = ['apple', 'samsung', 'xiaomi', 'sony'] as const;
// readonly tuple
// const phoneBrand: readonly ["apple", "samsung", "xiaomi", "sony"]
Enum
enum Color {
Red,
Green,
Blue,
}
const c: Color = Color.Red; // 0
enum SelectableButtonTypes {
Important = 'important',
Optional = 'optional',
Irrelevant = 'irrelevant',
}
有時物件搭配 enum 是,也會需要 as const
來讓 TS 更明確知道該物件值的型別:
// 範例來自 OneDegree 同事 Ken 與 Ivy
type Nullable<T> = T | null;
enum BE_ENUM_TIME_UNIT {
year = 'year',
month = 'month',
day = 'day',
}
const apiResponse = Math.random() > 0.05 ? BE_ENUM_TIME_UNIT.month : null;
enum FE_ENUM_TIME_UNIT {
year = 'year',
month = 'month',
day = 'day',
}
const map = {
[BE_ENUM_TIME_UNIT.year]: null,
[BE_ENUM_TIME_UNIT.month]: FE_ENUM_TIME_UNIT.month,
[BE_ENUM_TIME_UNIT.day]: FE_ENUM_TIME_UNIT.day,
};
const map2 = {
[BE_ENUM_TIME_UNIT.year]: null,
[BE_ENUM_TIME_UNIT.month]: FE_ENUM_TIME_UNIT.month,
[BE_ENUM_TIME_UNIT.day]: FE_ENUM_TIME_UNIT.day,
} as const;
// 因為沒有用 as const,所以 map[apiResponse] 推倒出來的型別是
// ERROR: Type 'FE_ENUM_TIME_UNIT.year' is not assignable to type 'Nullable<FE_ENUM_TIME_UNIT.month | FE_ENUM_TIME_UNIT.day>'
let unit: Nullable<FE_ENUM_TIME_UNIT.month | FE_ENUM_TIME_UNIT.day> = apiResponse
? map[apiResponse]
: null;
let unit2: Nullable<FE_ENUM_TIME_UNIT.month | FE_ENUM_TIME_UNIT.day> = apiResponse
? map2[apiResponse]
: null;
第一個例子(unit
),因為沒有在 map
的型別定義時 as const
,所以 map[apiResponse]
會被推導成 FE_ENUM_TIME_UNIT
:
const map: {
year: null;
month: FE_ENUM_TIME_UNIT;
day: FE_ENUM_TIME_UNIT;
};
const apiResponse: BE_ENUM_TIME_UNIT.month;
map[apiResponse]; // FE_ENUM_TIME_UNIT
導致這個型別(FE_ENUM_TIME_UNIT
)沒辦法同時滿足和 Nullable<FE_ENUM_TIME_UNIT.month | FE_ENUM_TIME_UNIT.day>
。
在第二個例子(unit2
)中,因為有在 map2
定義型別時加上 as const
,所以 map[apiResponse]
會被推導成 FE_ENUM_TIME_UNIT.month
:
const map2: {
readonly year: null;
readonly month: FE_ENUM_TIME_UNIT.month;
readonly day: FE_ENUM_TIME_UNIT.day;
};
const apiResponse: BE_ENUM_TIME_UNIT.month;
map[apiResponse]; // FE_ENUM_TIME_UNIT.month
而 FE_ENUM_TIME_UNIT.month
這個型別是可以滿足 Nullable<FE_ENUM_TIME_UNIT.month | FE_ENUM_TIME_UNIT.day>
的。
never
- never 可以想成是一個什麼都沒有的「空集合(empty set)」,它是 bottom type
// never 就是一個空
type A = 'string' | 'number' | never; // type A = "string" | "number"
// never 也屬於 any
type T = never extends any ? true : false; // type T = true
- never 使用的時間點是該函式會「卡在裡出不來」例如,無窮迴圈;或者「終止在函式裡」,例如,拋出錯誤。
- never 是任何型別的 prototype,因此不用再額外使用
number | never
這種寫法。 - never 是用來提醒開發者,這個函式執行後,程式將無法繼續往後執行。
function showNever(arg: string | number) {
if (typeof arg === 'string') {
arg.split(',');
} else if (typeof arg === 'number') {
arg.toFixed(2);
} else {
// here, arg is never
}
}
Unknown
unknown
和 never
相反,可以把 unknown
視為什麼都包含在內的全集合(Universe),它包含了所有可能性,屬於 Top Types。
unknown
的變數有可能是任何型別,但和 any
有一個關鍵的差異在於,開發者不能對 unknown
型別的變數進行任何操作,但 any
可以:
let isAny: any;
let isUnknown: unknown;
isAny.hello(); // 可以對 any 型別進行任何操作
isUnknown.hello(); // 不可以對 unknown 型別進行任何操作
Unknown 適合用在
- 當該變數不希望再被操作的情況
- 該 變數型別未知,需要在被限縮(narrow)後才能使用
因此,對於 unknown
型別,通常會搭配 narrow 或 type guard 使用。
真的必要的話才使用 Type Assertions 的方式:
let isUnknown: unknown = {
foo: 'foo',
hello: () => console.log('hello'),
};
// ❌ 錯誤:不可以對 unknown 型別進行任何操作
isUnknown.foo;
isUnknown.hello();
type Hello = {
foo: string;
hello: () => void;
};
// ⭕️ 正確:對 unknown 型別需要使用 Type Assertions
(<Hello>isUnknown).foo;
(isUnknown as Hello).hello();
any
一開始宣 告變數時,如果對變數不設值,且為註記型別,都會被視為是 any:
any
算是 TypeScript 中的一個「特例」,它有點難用集合的概念來解釋,因為它既是 Top Type 又是 Bottom Type 的型別。比較好的理解是,any
就是在告訴 TypeScript,你就放生我、不要管了吧!
let foo; // any
type A = 'string' | 'number' | any; // type A = any
Nullable Types
包含 null
和 undefined
:
// Not much else we can assign to these variables!
let u: undefined = undefined;
let n: null = null;
Object
任何除了原始 型別(primitive type)之外的,都可以算是 Object Type。原始型別包含:數值、字串、布林、symbol
、null
、undefined
。
使用 Object Literal 建立物件
當我們使用 Object literal 建立物件而沒有宣告型別時,實際上 TS 是建立一個 Type,而不是套用 object 型別:
let foo = {
s: 'string',
n: 3,
b: true,
};
實際上這個 foo
套用了一個 Type:
type Foo = {
s: string;
n: number;
b: boolean;
};
因此將不能對 foo
的這物件去新增屬性,例如,foo.bar = 'bar'
是不被允許的。