概述
- 類型就是一組值的集合
類型擦除
TS 轉化成 JS
方法
-
npm i -g esbuild
=>esbuild 1.ts > 1.js
=> 快(不檢查 TS 語法) -
npm i -g @swc/cli @swc/core
=>swc 1.ts -o 1.js
=> 快(不檢查 TS 語法) -
npm i -g typescript
=>tsc 1.ts
數據類型
- null/undefined
- string/number/boolean
- bigint/symbol
- object
- void
- any/unknow
- never
- enum
- 自定義類型 => type(類型別名)/interface
TS 描述對象
- 使用 class/constructor 描述
- 使用 type/interface 描述
type A = {
// 索引簽名
[key: string]: number
}
// Record 泛型
type B = Record<string, number>;
const n: BigInt = 100n;
console.log(n); // 100n
const symbol: Symbol = Symbol("s");
console.log(symbol); // Symbol(s)
const date: Date = new Date()
const regexp: RegExp = /ab+c/;
const regexp1: RegExp = new RegExp("/\d+/");
const map: Map<string, number> = new Map();
map.set("a", 1);
const set: Set<number> = new Set();
set.add(1);
TS 描述數組
-
type A = string[]
==type A = Array<string>
- 元組 =>
type A = [string, string, number]
=> length == 3 -
type A = [1, 2, 3 | 4]
=>const a: A = [1, 2, 3]
TS 描述函數
-
type FnA = (a: number, b: number) => string
=>const a: FnA = () => "hello world";
type FnReturnVoid = () => void;
type FnReturnUndefined = () => undefined;
const f1: FnReturnVoid = () => {
console.log("hi");
}
const f2: FnReturnUndefined = () => {
console.log("hi");
// 此處需要顯式聲明
return undefined;
}
type Person = {
name: string,
age: number,
sayHi: FnWithThis
}
type FnWithThis = (this: Person, name: string) => void;
// 此處如果使用箭頭函數,則 this 指向有問題
const sayHi: FnWithThis = function () {
console.log("hi " + this.name)
}
const ming: Person = {
name: "ming",
age: 18,
sayHi: sayHi
}
ming.sayHi("Jack");
sayHi.call(ming, "Jack");
any vs unknown
- any => 全集 => 不做任何類型檢查
- any 不等于所有類型的聯合 => 反證法:聯合類型如果沒有區分類型,只能使用交集的屬性|方法
- TS 絕大部分規則對 any 不生效 =>
const fn = (a: any) => {const b: never = a;}
=> 報錯 - unknown => 未知集 => 使用時需要先收窄類型 => 所有類型的聯合(聯合類型)
never
- 空集
- 不應該出現的類型
enum
- 使用一個有意義的名字代表一個狀態
enum A {
TODO = 0,
DONE,
ARCHIVED,
DELETED
}
// 類型擦除時 A.DONE 會變成 1
let status: A = A.DONE;
- 通過使用位運算可以很方便的將權限進行組合和對比
enum Permission {
NONE = 0, // 0000
READ = 1 << 0, // 0001
WRITE = 1 << 1, // 0010
DELETE = 1 << 2, // 0100
MANAGE = READ | WRITE | DELETE // 0111
}
type User = {
permission: Permission
}
const user: User = {
permission: 0b0011
}
if ((user.permission & Permission.WRITE) === Permission.WRITE) {
console.log("擁有寫權限")
}
- 使用 enum 做字符串映射字符串時可以使用類型別名代替
// enum Status {
// done = 'done',
// process = 'process',
// start = 'start'
// }
type Status = 'done' | 'process' | 'start';
type
- 類型別名
- 聲明一個別名,并不是一個新的類型
-
type 的值都是類型 => type 的值里面不能有任何值 =>
type nan = NaN // 這是錯誤的
=> 'NaN' refers to a value, but is being used as a type here -
type A = {name: string}
=> 表示 name 為 string 的對象
type A = { name: string };
const a1 = { name: 'ming', age: 18 };
// 此時 TS 不會做嚴格檢查
const a2: A = a1;
// Error => TS 第一次聲明的時候會進行嚴格檢查
const a: A = { name: 'ming', age: 18 }
// 其中的 0、null、undefined、''、false 都是類型
type Falsy = 0 | null | undefined | '' | false;
// 類型A是一個集合,集合中只有一個0
type A = 0;
type FnWithProp = {
(a: number, b: number): number,
props1: number;
}
const fn: FnWithProp = (x, y) => x + y;
fn.props1 = 101;
console.log(fn);
console.log(fn.props1)
interface
- 聲明接口
- 描述對象的屬性 => declare the shapes of object
type vs interface
- interface 只描述對象,type 則描述所有類型
- interface 是類型聲明,type 是別名
type A = string; type B = A; // B is string interface C extends Date { } type D = C; // D is C
- interface 可以進行 merging(擴展),type 不可以重新賦值 => 對外 API 盡量使用 interface,方便擴展。對內 API 盡量使用 type(case: props),防止代碼分散
- interface extends 時如果屬性沖突,直接報錯,type 會聯合類型到 never
Object vs object
- Object 包含 Number/String/Boolean 包裝類型
- object 不包含包裝類型
void vs null vs undefined
- void 不可以賦值給 null 和 undefined
- undefined 可以賦值給 void
- 其余需要看配置 => strictNullChecks
let a: void
// 此時 void = null | undefined
if (typeof a === 'object') {
// a is null
console.log(a);
} else if (typeof a === 'undefined') {
// a is undefined
console.log(a);
} else {
// a is never
console.log(a);
}
// 此時 void = undefined
if (typeof a === 'undefined') {
// a is undefined
console.log(a);
} else if (typeof a === 'object') {
// a is never
console.log(a);
} else {
// a is never
console.log(a);
}
Assignment
Assignment
TS 類型系統運算
聯合類型
- union types => 并集
JS 語言特性區分類型
- 使用 JS 的語言特性 typeof + instanceof + in 進行類型收窄
const fn = (a: number | string) => {
// 類型收窄 => Narrowing
if (typeof a === 'number') {
a.toFixed(2);
} else if (typeof a === 'string') {
a.split(',');
} else {
// a === never
console.log(a);
}
}
type Person = {
name: string;
}
type Animal = {
x: string;
}
const f1 = (param: Person | Animal) => {
// 使用 in 進行類型收窄
if ('name' in param) {
console.log(param.name);
} else {
console.log(param.x);
}
}
類型謂詞 is
type Rect = {
height: number,
width: number
}
type Circle = {
center: [ number, number ],
radius: number
}
const fn = (a: Rect | Circle) => {
if (isRect(a)) {
// a is Rect
console.log(a.height);
} else if (isCircle(a)) {
// a is Circle
console.log(a.radius);
} else {
// a is never
console.log(a);
}
}
function isRect(x: Rect | Circle): x is Rect {
return 'height' in x && 'width' in x;
}
// 此處 x is Circle 不能寫在左側 => const isCircle: (x: Rect | Circle) => x is Circle = x => 'radius' in x && 'center' in x;
const isCircle = (x: Rect | Circle): x is Circle => {
return 'radius' in x && 'center' in x;
}
可辨別聯合 Discriminated Unions
- 讓復雜類型的收窄變成簡單類型的對比
type Circle = { kind: "Circle", center: [ number, number ] };
type Square = { kind: "Square", sideLength: number };
type Shape = Circle | Square;
const f1 = (a: string | Shape) => {
if (typeof a === 'string') {
// a is string
a.split(',');
} else if (a.kind === 'Circle') {
// a is Circle
console.log(a.center);
} else {
// a is Square
console.log(a.sideLength);
}
}
斷言 as
// a is string
let a = 'hi';
// b is hi
const b = 'hi';
// c is hi
let c = 'hi' as const;
// array is (number | string)[] => 對于引用類型在 TS 中 const 相當于 let
const array = [ 1, 'hi' ];
// array1 is readonly [1, 'hi']
const array1 = [ 1, 'hi' ];
// Error: Property 'push' does not exist on type 'readonly [1, "hi"]'.
array1.push(2);
交叉類型 intersection types
- 交叉類型常用于有交集的類型 A B
- 如果 A、B 無交集,可能得到 never,也可能屬性為 never
type
type Left = {
left: string;
}
type Right = {
right: string;
}
type C = Left | Right;
type D = Left & Right;
const c: C = {
left: "left"
}
const d: D = {
left: 'left',
right: 'right'
}
type Person = {
name: string;
age: number;
id: string
}
// 此處 id 屬性的類型是 string & number => id is never
type User = Person & {
id: number;
email: string;
}
const user: User = {
id: 1 as never,
name: 'ming',
age: 18,
email: 'email'
}
// userIsNever is never
type UserIsNever = { id: 'A' } & { id: 'B' };
interface
interface Colorful {
color: string;
}
interface Circle {
radius: number
}
type ColorfulCircle = Colorful & Circle;
interface Person {
id: string;
name: string;
}
interface User extends Person {
// Error: type 'number' is not assignable to type 'string'.
// id: number;
email: string;
}
神奇
type A = {
method: (n: number) => void;
}
type B = {
method: (n: string) => void;
} & A;
const b: B = {
// n is any
method: (n) => {
console.log(n);
}
}
type F1 = (n: number) => void;
type F2 = (n: string) => void;
type X = F1 & F2;
const x: X = (n) => {
// n is any
console.log(n);
n.split(',')
n.toFixed(2);
}
類型兼容 & 賦值
基本類型
type A = string | number;
const a: A = 'hi';
普通對象
type Person = {
name: string;
age: number;
}
let user = { name: 'ming', age: 18, id: 1 };
let p: Person;
p = user;
// type User = {name: string; age: number; id: nubmer}
type User = typeof user;
接口
interface Parent {
x: string;
}
interface Sub extends Parent {
y: string;
}
let sub: Sub = {
x: '1',
y: '2'
};
let p: Parent;
p = sub;
函數
- 函數包括參數和返回值
- 參數少的函數可以賦值給參數多的函數
- 對參數要求少函數可以賦值給對參數要求多的函數
深入對象語法
索引簽名 Index Signature
type Hash = {
[k: string]: unknown;
length: number;
}
映射類型 Mapped Type
- 多用于泛型
type Hash = {
// 不能再寫其他屬性
[key in string]: unknown;
// length: number; => error
}
只讀屬性 readonly
interface User {
readonly id: number;
age?: number;
}
const user: User = {
id: 1,
age: 18,
}
// Error: Cannot assign to 'id' because it is a read-only property
user.id = 2;
深入函數語法
對象語法全部適用于函數
type F = {
(a: number, b: number): number;
readonly count?: number;
}
type F1 = (a: number, b: number) => number;
const f: F = (x, y) => x + y;
聲明函數及其類型
// First
type F1 = (a: number, b: number) => number;
const f1: F1 = (a, b) => a + b;
// Second
const f2 = (a: number, b: number): number => a + b;
type F2 = typeof f2;
// Third
function f3(this: unknown, a: number, b: number): number {
return a + b;
}
type F3 = typeof f3;
// Fourth
const f4 = function (this: unknown, a: number, b: number): number {
return a + b;
}
type F4 = typeof f4;
類型謂詞
可選參數
參數默認值
function addEventListener(eventType: string, fn: (this: HTMLElement, e: Event) => void, useCapture = false) {
const element = {} as HTMLElement;
const event = {} as Event;
fn.call(element, event);
}
addEventListener('click', () => console.log(1));
重載 overload
- overload => 同名函數參數不同
- 參數類型不同 => 聯合類型
- 參數個數不同 => 考慮是用 overload
- 參數類型和個數都不同
function createDate(timestamp: number): Date;
function createDate(year: number, month: number, day: number): Date;
function createDate(a: number, b?: number, c?: number): Date {
if (a !== undefined && b !== undefined && c !== undefined) {
return new Date(a, b, c);
} else if (a !== undefined && b === undefined && c === undefined) {
return new Date(a);
} else {
throw new Error('Params is invalid!');
}
}
// Error: No overload expects 2 arguments, but overloads do exist that expect either 1 or 3 arguments.
createDate(2022, 10);
展開參數
function sum(...array: number[]) {
// Error: Argument of type 'number[]' is not assignable to parameter of type 'number'.
// f(array)
f(...array);
f.apply(null, array);
}
function f(...array: number[]) {
console.log(array);
}
as const
function add(a: number, b: number): number {
return a + b;
}
// arr is number[]
const arr = [ 1, 2 ];
// Error: A spread argument must either have a tuple type or be passed to a rest parameter.
add(...arr);
// arr1 is readonly [1, 2]
const arr1 = [ 1, 2 ] as const;
add(...arr1);
解構
type Config = {
url: string;
method: "GET" | "POST" | "PUT" | "DELETE";
body?: unknown;
headers?: unknown
}
// rest is {body?: unknown; headers?: unknown}
function ajax({ url, method, ...rest }: Config = { url: '', method: "GET" }) {
}
function ajax1({ url, method, ...rest } = { url: '', method: "GET" } as Config) {
}
泛型 Generic Type
- 推后執行的、部分待定的類型
type F<A, B> = A | B;
// Result is number | string
type Result = F<number, string>;
function echo(msg: number | boolean | string) {
return msg;
}
// F is (msg: number | boolean | string) => number | boolean | string;
type F = typeof echo;
類型參數默認值
interface Hash<V = string> {
[k in string]: V;
}
條件類型 Conditional Type
- 以下兩條僅對泛型有效
- 如果 T 是 never,則表達式的值為 never
- 如果 T 是聯合類型,則分開計算
type LikeString<T> = T extends string ? true : false;
type R1 = LikeString<'hi'>; // true
type R2 = LikeString<true>; // false
type X = LikeString<never>; // never
type Y = LikeString<string | number>; // Y is boolean
type ToArray<T> = T extends unknown ? T[] : never;
type Result = ToArray<number | string>; // Result is number[] | string[]
type Result1 = ToArray<never>; // Result1 is never
type Z = never extends unknown ? 1 : 2; // Z is 1
// 此處不能對應 string -> string[] number -> number[]
type U = string | number extends unknown ? string[] | number[] : 1;
keyof
type Person = { name: string, age: number };
type GetKeys<T> = keyof T;
type Result = GetKeys<Person>; // Result is name | age
泛型約束
type GetKeyType<T, K extends keyof T> = T[K];
type Result1 = GetKeyType<Person, "name">; // string
type Test = Person['name']; // Test is string
內置
Readonly
type Person = { name: string, age: number };
// type Result = {
// readonly name: string;
// readonly age: number;
// }
type Result = Readonly<Person>;
// type Readonly<T> = {
// readonly [k in keyof T]: T[k];
// }
Partial
type Person = { name: string, age: number };
// type Result = {
// name?: string | undefined;
// age?: number | undefined;
// }
type Result = Partial<Person>;
// type Partial<T> = {
// [k in keyof T]?: T[k];
// }
Required
type Person = { name: string, age: number, email?: string };
// type Result = {
// name: string;
// age: number;
// email: string;
// }
type Result = Required<Person>;
// type Required<T> = {
// [k in keyof T]-?: T[k];
// }
Record
type Result = Record<string, number>;
// type Record<K extends string | number | symbol, T> = {
// [k in K]: T;
// }
Exclude
type Result = Exclude<1 | 2 | 3, 1 | 2>; // Result is 3
// type Exclude<T, K> = T extends K ? never : T;
Extract
type Result = Extract<1 | 2 | 4, 2 | 3>; // Result is 3
// type Extract<T, K> = T extends K ? T : never;
Pick
type Person = { name: string, age: number, email?: string };
// type Result = {
// name: string | number;
// age: string | number;
// }
type Result = Pick<Person, 'name' | 'age'>;
// type Pick<T, K extends keyof T> = {
// [key in K]: T[K];
// }
Omit
type Person = { name: string, age: number, email?: string };
// type Result = {
// email?: string | undefined;
// }
type Result = Omit<Person, 'name' | 'age'>;
// type Omit<T, K> = {
// [k in keyof T as (k extends K ? never : k)]: T;
// }
// 使用 Pick
// type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
-readonly
type Person = { name: string, age: number, email?: string };
// type Result = {
// name: Readonly<Person>;
// age: Readonly<Person>;
// email?: Readonly<Person> | undefined;
// }
type Result = Mutable<Readonly<Person>>;
type Mutable<T> = {
-readonly [key in keyof T]: T;
}
class
class Point {
x: number;
y: number;
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
// 等價于
class Point1 {
constructor(public x = 0, public y = 0) {
}
}
class Hash {
[key: string]: unknown;
set(key: string, value: unknown) {
this[key] = value;
}
get(key: string) {
return this[key];
}
}
constructor reload
class Point {
x!: number;
y!: number;
constructor(x: number, y: number);
constructor(str: string);
constructor(xs: number | string, y?: number) {
if (typeof xs === 'number' && typeof y == 'number') {
this.x = xs;
this.y = y;
} else if (typeof xs === 'string') {
this.x = parseFloat(xs.split(',')[0]);
this.x = parseFloat(xs.split(',')[1]);
}
}
}
implements
- implements 僅僅會做類型檢查,其余不會進行任何操作
interface Person {
name: string;
sayHi: (target: Person) => void;
}
interface Taggable {
tags: string[];
addTag: (tag: string) => void;
removeTag: (tag: string) => void;
}
class User implements Person, Taggable {
constructor(public name: string, public tags: string[] = []) {
}
sayHi(target: Person) {
console.log('Hi ' + target.name);
}
addTag(tag: string) {
this.tags.push(tag);
}
removeTag(tag: string) {
this.tags.splice(this.tags.indexOf(tag), 1);
}
}
const ming = new User('ming');
ming.sayHi(ming);
declare
- 重寫屬性
class Person {
friend?: Person;
constructor(public name: string, friend?: Person) {
this.friend = friend;
}
}
class User extends Person {
// Error: Property 'friend' will overwrite the base property in 'Person'. If this is intentional, add an initializer. Otherwise, add a 'declare' modifier or remove the redundant declaration.
// friend?: User;
declare friend?: User;
constructor(public id: string, name: string, friend?: User) {
super(name, friend);
}
}
const u1 = new User("1", "jack");
const u2 = new User("2", "george", u1);
// 如果沒有 declare => Friend is Person | undefined
// 如果有 declare => Friend is User | undefined
type Friend = typeof u2.friend;
public & private & protected
- public => 類外可見
- private => 類內可見 => 類型擦除之后就沒有 private 了,在 js 中仍然可以使用該屬性
- protected => 子類可見
-
#<var>
=> 真私有屬性 => 類型擦除之后會保留,在 js 中不可使用該屬性
static
- 通過類型訪問
- 類屬性/靜態屬性
- 不能有 static name => 類是使用函數模擬的,函數本身有 name 屬性
- static block
class Foo {
static #count = 0;
static {
const count = loadFromLocalStorage() || 0;
Foo.#count += count;
}
constructor() {
console.log(Foo.#count);
}
}
抽象類
- 抽象類不能直接實例化
abstract class A {
abstract name: string;
age: string;
constructor(age: string) {
this.age = age;
}
abstract getName(): string;
printName() {
console.log("Hi, " + this.getName());
}
}
類作為參數
class Person {
constructor(public name: string) {
this.name = name;
}
}
function f(X: typeof Person) {
const p = new X('George');
}
f(Person)
// new 表示傳入的參數必須是 class
function f2(X: new (name: string) => Person) {
const p = new X("George");
}
f2(Person);
類型體操
// if (A <= B) true else false
type A = 1;
type B = 1 | 2;
type Result = A extends B ? true : false;
// 空元組
type A = [];
type IsEmptyArray<Arr extends unknown[]> = Arr['length'] extends 0 ? true : false;
type Result = IsEmptyArray<[]>;
// 非空元組
type B = [ 1 ];
type NotEmpty<Arr extends unknown[]> = Arr extends [ ...unknown[], unknown ]
? true
: false;
type NotEmpty1<Arr extends unknown[]> = Arr extends [ ...infer X, infer Last ]
? true
: false;
type Result1 = NotEmpty<B>;
遞歸
- TS 最多遞歸 48 層
type A = [ 1, 2, 3, 4 ];
type Reverse<Arr extends unknown[]> = Arr extends [ ...infer Rest, infer Last ] ? [ Last, ...Reverse<Rest> ] : Arr;
type Result = Reverse<A>;
模式匹配 + infer
- infer => decoratively introduce a new generic type variable
type A = [ 1, 2, 3, 4 ];
// Result1 is 1
type Result1 = A extends [ infer First, ...infer Rest ] ? First : never;
// Result2 is [2, 3, 4]
type Result2 = A extends [ string, ...infer Rest ] ? Rest : never;
元組體操
// 將元組加長
type A = [ 1 ];
type B = [ ...A, number ];
type C = [ ...B, 'hi' ];
type D = [ ...B, ...C ];
// get Last type in Tuple
type Last<T extends unknown[]> = T extends [ ...unknown[], infer Last ] ? Last : never;
type Hi = Last<C>; // Hi is 'hi'
// Error => TS 沒有減法
// type Last<T extends unknown[]> = T[T["length"] - 1];
字符串體操
Capitalize & Uncapitalize & Uppercase & Lowercase
- TS 內部實現
- Capitalize => 首字母大寫
- Uncapitalize => 首字母小寫
- Uppercase => 全部大寫
- Lowercase => 全部小寫
type A = "george";
type B = Capitalize<A>; // B is George
type C = 'hi' | 'george';
type D = Capitalize<C>; // D is 'Hi' | 'George'
模板字符串
type A = "Hi";
type B = "George";
type Result = `${A} ${B}`;
模式匹配
type A = "Hi George";
type First<T extends string> = T extends `${infer F}${string}` ? F : never;
type A = "Hi George";
type LastOfTuple<T extends unknown[]> = T extends [ ...infer _, infer L ] ? L : never;
// String to Tuple
type StringToTuple<S extends string> = S extends `${infer F}${infer Rest}` ? [ F, ...StringToTuple<Rest> ] : [];
type Last<T extends string> = LastOfTuple<StringToTuple<T>>;
type Result = First<A>; // Result is H
type ResultOfLast = Last<A>; // Result is e
String to Union
- 聯合類型自動去重
type A = "Hi George";
type StringToUnion<S extends string> = S extends `${infer First}${infer Rest}` ? First | StringToUnion<Rest> : never;
type Result = StringToUnion<A>;
TSX
- 在 TSX 中標簽式斷言會和標簽沖突,此時使用 as 斷言
知識點
- 在 TSX 中 input 的 onChange 實際上調用的是 oninput 事件
- onchange => 觸發時機是失去焦點時
- oninput => 觸發時機是輸入時