TypeScript 學習筆記

Hello, 各位勇敢的小夥伴, 你們好, 我是大家的嘴強王者小五, 身體健康, 腦子沒病.html

本人有豐富的脫髮技巧, 能讓你一躍成爲資深大咖.前端

一看就會一寫就廢是本人的主旨, 菜到摳腳是本人的特色, 卑微中透着一絲絲剛強, 傻人有傻福是對我最大的安慰.react

歡迎來到小五隨筆系列TypeScript 學習筆記.typescript

導讀

本文初衷爲筆者在使用一段時間 TS 後, 對所學所想作一個記錄. 文章內容較爲基礎, 適合做爲入門級教程學習; 且涵蓋內容並不全面, 缺失包含類在內的諸多內容. 若是各位看官不介意以上幾點, 歡迎與筆者一同進入這嚴謹卻又妙不可言的奇幻旅途.數組

基礎類型

Boolean - 布爾

let flag: boolean = false;

Number - 數字

let num: number = 10;

👺 擴展: number類型還支持 二進制(0b開頭)八進制(0o開頭)十六進制(0x開頭) 字面量. 運算或輸出時會轉換爲對應的十進制.app

let num02: number = 0b1010;
let num08: number = 0o744;
let num16: number = 0xf00d;

String - 字符串

let str: string = '黃刀小五';

Array - 數組

數組共有兩種定義方式: Array<T>T[] (T爲該數組每項的元素類型)函數

let numArr: number[] = [1, 2, 3];
let numArr: Array<number> = [1, 2, 3];

Tuple - 元組

👺 特色: <數組>, <長度已知>, <每項元素類型不盡相同> post

😼 就是一個給每項都定義數據類型的數組學習

let tuple: [string, string, number, boolean?] = ['No.1', '黃刀小五', 18];

🦥 應用:ui

  • React Hook 的 useState
import { useState } from 'react';
const [loading, setLoading] = useState<boolean>(false); // 這裏類型可省略, 詳見後文類型推斷
  • csv 數據格式
type Touple = [string, number, string];
let csvData: Touple[] = [['黃刀小五', 18, '男'], ['二狗子', 14, '男']];

👺 擴展: 元組越界

let tuple: [number, string] = [1, '黃刀小五'];
tuple.push(2); // ✅ right 元組越界時, 該項類型至關於對元組的各項類型作聯合

/* 過程以下, 看不懂的看官莫慌, 請先看後文 */
type UnionType<T> = T extends (infer P)[] ? P : never;
type TupleType = UnionType<typeof tuple>; // number | string

Enum - 枚舉

enum Active {
  inactive,
  active,
}

enum Fruit {
  apple = 'apple',
  orange = 'orange',
  banana = 'banana',
}

若枚舉類型未指定值或指定的值爲number類型, 如上述 Active, 可對其進行雙向取值: Active[0]Active['active'], 其映射以下【👇】

{
  0: 'inactive',
  1: 'active',
  inactive: 0,
  active: 1,
}

可對枚舉的其中一項進行指定數值(一般爲第一項), 其他項會順序遞增

enum Type {
  active = 1,
  inactive,
}

{ // 對應映射爲
  1: 'active',
  2: 'inactive',
  active: 1,
  inactive: 2,
}

😼 tips: 建議採用賦值形式的枚舉. 可讀性更高, 如上述 Fruit

🦥 應用:

  • 結合 switch ... case 使用:
enum ActionType {
  ADD = 'ADD',
  EDIT = 'EDIT',
  DELETE = 'DELETE',
}

const reducer = (type: ActionType) => {
  switch(type) {
    case ActionType.ADD:
      // xxx
      break
    case ActionType.EDIT:
      // xxx
      break
    ...
  }
}

reducer(ActionType.ADD); // ✅ right
let params = 'ADD';
reducer(params); // ❎ error (類型「string」的參數不能賦給類型「ActionType」的參數)

/* 😼 tips: 非賦值形式你們可自行嘗試 */
  • 定義類型映射 或 定義一組常量
enum Status { // 類型映射
  unprocessed = 1, // 未處理
  processed, // 已處理
  refused, // 已拒絕
}

enum Fruit { // 常量
  apple = '蘋果',
  orange = '橘子',
  banana = '香蕉',
}

👺 擴展: keyof typeof Enum, 可將枚舉類型轉換爲聯合類型; 相信我, 你會用到的😏

enum ActionType {
  ADD,
  EDIT,
  DELETE,
}
type ActionTypeConst = keyof typeof ActionType // 'ADD' | 'EDIT' | 'DELETE'

Any - 任意類型

let value: any;

😼 tips: 食物鏈最頂端, 應儘可能減小 any 的使用.

Unknown - 未知類型

let value: unknown;

unknown 表示未知類型, 與 any 不一樣的是沒法對 unknown 類型執行任何操做. 仔細思考下 <any: 任意類型>、 <unknown: 未知類型> 的區別.

👉 以 numbertoFixed() 方法舉例:

  • unknown 表明我不知道這是什麼類型, 而只有 numbertoFixed() 方法, 故不能使用;
  • any 表明我能夠是任意類型, 此時調用 toFixed() 方法的我就是 number 類型;

😼 tips: 如想使用 any, 請先考慮是否可用 unknown 代替. (搭配後文類型保護可安心食用)

Void - 無類型

經常使用於沒有具體返回值的函數

const fn = (str: string): void => {
  // 執行xxx事件
}

Null 和 Undefined

let u: undefined = undefined;
let n: null = null;

這兩個不知道要說點啥, 就補充如下兩點吧 (然而與 TS 沒啥關係)

  1. Number(undefined) => NaN, Number(null) => 0
  2. const fn = (arg?: string) => { ... } arg的類型是 string | undefined

Never - 永不返回

let n: never;
  • never 表示那些永遠不存在的值, 當咱們不想捕獲當前值時, 可以使用 never;
  • 任何類型都不可賦值給 never, 包括 any;
  • never | T = T 此特性可用來過濾掉不須要的值;

🦥 應用:

  • 作類型檢查
type Type = string | number;

const fn = (arg: Type) => {
  if (typeof arg === 'string') { ... }
  else if (typeof arg === 'number') { ... }
  else { const check: never = arg; }
}

如上, 此時永遠不會走到 else, 下面咱們作如下操做:

- type Type = string | number;
+ type Type = string | number | boolean;

* 此時 else 中的 check 會報錯 (不能將類型「boolean」分配給類型「never」)

類型推論

若是沒有指定類型, TS 會根據類型推論推斷出一個類型.

let val; // 推論成: let val: any;
let num = 10; // 推論成: let num: number = 10;
num = '黃刀小五'; // ❎ error (不能將類型「string」分配給類型「number」)

🦥 應用: <單一靜態類型 - 如上述num><函數返回值 - 通常狀況都可正確推斷其返回值類型, 不用額外指定><循環中的子元素>

😼 tips: 若是 TS 能正確推斷出其類型, 咱們可採用類型推論而沒必要定義類型.

類型斷言

類型斷言用來告訴編譯器 「我知道本身在幹什麼」, 有 尖括號as 兩種寫法. 在 $tsx$ 語法中, 只支持 as.

* 😼 tips: 下面咱們使用數組來舉個例子, 實際場景中應使用元組.

type Key = string | number;
let arr: Key[] = ['黃刀小五', 18];

// 不能將類型「Key」分配給類型「string」
- let name = arr[0];
- console.log(name.length);

// 使用類型斷言
+ let name = arr[0] as string;
+ let name = <string>arr[0];
+ console.log(name.length);

Interface - 接口

接口用來定義對象的類型

interface User {
  readonly id: number; // 只讀屬性, 不可修改
  name: string; // 必須屬性
  desc?: string; // 可選屬性
  say?: (name: string) => void; // 方法
}

const say = (name: string) => { ... }
let user: User = {
  id: 1,
  name: '黃刀小五',
  say,
}

user.id = 2; // ❎ error (沒法分配到 "id" ,由於它是隻讀屬性)

索引簽名 - 使接口更加靈活

interface User {
  [key: string]: string; // 表示 key 爲 string, value 爲 string 的任意屬性
}

let user:User = {
  name: '黃刀小五',
  desc: '菜雞前端一隻',
}

😼 tips: 全部成員都必須符合索引簽名的特徵, 索引簽名參數類型必須爲 string | number

interface User {
  [key: string]: string;
  age: number; // ❎ error (類型「number」的屬性「age」不能賦給字符串索引類型「string」。)
}

接口合併

interface User {
  name: string;
}
interface User {
  age: number;
}
/* 二者會合併爲👇 */
interface User {
  name: string;
  age: number;
}

接口繼承

關鍵字: extends

interface Person {
  name: string;
}
interface User {
  age: number;
}
interface Student extends Person, User {
  desc: string;
}
/* Student接口格式以下👇 */
interface Student {
  name: string;
  age: number;
  desc: string;
}

😼 tips: 接口和類型別名都可使用的狀況下使用接口

🤔 思考: 如何定義一個樹形結構

interface Tree {
  key: number;
  value: string;
  child?: Tree[];
}
let tree: Tree[] = [];

類型別名

顧名思義, 爲該類型取一個新的名字

type Key = string | number;

與 Interface 對比

  • type 不支持繼承和聲明合併, interface 能夠, 參考上文;
  • type 更爲通用, 右側能夠是任意類型, interface 主要用於定義對象;
  • typeinterface 都可使用的狀況下使用 interface;

聯合類型與交叉類型

  • 聯合類型 (A | B)
type Key = string | number; // 表明 string 或 number 類型

😼 tips: 聯合類型能夠用來聲明具體的值

type Status = 'active' | 'inactive';
  • 交叉類型 (A & B)
interface User {
  name: string;
}
interface Student {
  age: number;
}
type Blogger = User & Student;

/* Blogger類型格式以下👇 */
{
  name: string;
  age: number;
}

😼 tips: 兩個基礎類型作交叉, 會生成 never 類型

type Key = string & number; // never, 沒有類型能夠知足便是 string 又是 number

類型查找

類型查找能夠提取對象類型上某一屬性的類型

interface Person {
  User: {
    name: string;
    age?: number;
  }
}
type User = Person['User']

😼 tips: 經常使用於第三方庫類型沒法引用的場合

經常使用關鍵字

const

👺 配合類型斷言 as 來聲明常量

type Status = 'active' | 'inactive';
const fn = (status: Status) => { ... }

- let status = 'active'; // 此時 'active' 被解析爲字符串而很是量
- fn(status); // ❎ error (類型「string」的參數不能賦給類型「Status」的參數)

* 如下3種等價, 都可將 'active' 解析爲常量
+ let status = 'active' as const;
+ const status = 'active';
+ let status: Status = 'active';
+ fn(status); // ✅ right

😼 tips: 第三方插件定義的常量常常須要配合 as const 使用呦

typeof

👺 用來獲取變量的類型

let str = '黃刀小五';
type Str = typeof str; // type Str = string

let user = { name: '黃刀小五' }
type User = typeof user; // type User = { name: string; }

keyof

👺 用來提取對象類型的 key

interface User {
  name: string;
  age?: number;
}
type Key = keyof User; // type Key = 'name' | 'age'

類型保護

爲何須要類型保護

const fn = (value: string | number) => {
  if (value.length) { ... } // ❎ error (類型「string | number」上不存在屬性「length」)
}

上述代碼, 若是想在 value: string 時執行一段邏輯要怎麼辦呢?

此時就須要類型保護了, 使用類型保護後, 當前代碼段會按照事先所指定的類型執行.

👺 typeof

const fn = (value: string | number) => {
  if (typeof value === 'string') { // 利用 typeof 限制 value 的類型爲 string
    console.log(value.length);
  }
}

👺 is

const isString = (x: unknown): x is string => {
  return typeof x === 'string';
}
const fn = (value: string | number) => {
  if (isString(value)) console.log(value.length); // value is string
}

👺 in

表示某屬性是否在當前對象中存在

interface User {
  name: string;
  age?: number;
}
const fn = (args: User) => {
  if ('age' in args) { ... }
  else { ... }
}

fn({ name: '黃刀小五', age: 18 }); // 執行 if 語句
fn({ name: '黃刀小五' }); // 執行 else 語句

泛型

👺 做用: 用於作代碼複用

先來看個例子

const fn = (value: number): number => value;

🤔 思考 若是此時我想傳入一個 string 類型並返回一個 string 類型呢

const fn = <T>(value: T): T => value;
fn<string>('黃刀小五'); // const fn: <string>(value: string) => string

* 😼 tips: 可根據 類型推斷 推斷出其爲 string 類型, 而不用特地指定 -> `fn('黃刀小五');`

以上, 一個泛型就實現好了, 其能夠將類型做爲一個參數, 在調用時傳入類型進行指定.

👺 注意: 在 $tsx$ 中, <T> 會被解析成標籤, 可以使用下面的寫法:

const fn = <T extends {}>(value: T): T => value; // extends也可用於縮小T的範圍
const fn = <T,>(value: T): T => value;
  • 傳入多個類型:
const fn = <T, U>(type: T, value: U): U => { ... };
fn<boolean, string>(true, '黃刀小五');

泛型的做用至關之廣, 如定義一個接口:

interface Teacher<T> {
  readonly id: number;
  name: string;
  student?: T[];
}
interface Student {
  readonly id: number;
  name: string;
  age?: number;
}
let teahcer: Teacher<Student> = {
  id: 1,
  name: '黃刀小五',
  student: [{
    id: 1001,
    name: '二狗子',
    age: 14,
  }]
}

經常使用語法糖

下面咱們來用上述知識實現下 TS 中封裝好的語法糖 (舒適提示: 建議先搞懂上文, 至少搞懂泛型在閱讀下面內容) look down 👇

Readonly

👺 將對象類型的屬性均變爲只讀

  • 代碼實現
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};
  • Demo
interface Person {
  readonly name: string;
  age: number;
  desc?: string;
}
let person: Readonly<Person> = {
  name: '黃刀小五',
  age: 18,
}
person.age = 19; // ❎ error (沒法分配到 "age" ,由於它是隻讀屬性)

Partial

👺 將對象類型的屬性均變爲可選

  • 代碼實現
type Partial<T> = {
  [K in keyof T]?: T[K];
};
  • Demo
interface Person {
  readonly name: string;
  age: number;
  desc?: string;
}
let person: Partial<Person> = {}; // 此時全部屬性均爲可選

Required

👺 將對象類型的屬性均變爲必須

  • 代碼實現
type Required<T> = {
  [K in keyof T]-?: T[K];
};

😼 tips: -? 表示去掉可選符號 ?, 此符號 (-) 一樣可用於其它位置, 如: -readonly 可去掉只讀屬性等.

  • Demo
interface Person {
  readonly name: string;
  age: number;
  desc?: string;
}
let person: Required<Person>> = {
  name: '黃刀小五',
  age: 18,
  desc: '基於搜索引擎的複製粘貼工程獅',
}; // 此時全部屬性均爲必須

Record

👺 將一個類型的全部屬性值映射到另外一個類型上並建立一個新的類型

  • 代碼實現
type Record<K extends string, T> = {
  [P in K]: T;
};
  • Demo
interface Person {
  readonly name: string;
  age: number;
  desc?: string;
}
type Kind = 'teacher' | 'student';
let person: Record<Kind, Person> = {
  teacher: {
    name: '黃刀小五',
    age: 18,
  },
  student: {
    name: '二狗子',
    age: 14,
  }
}; // 將 Person 類型映射到 Kind 類型中

/* Record<Kind, Person> 至關於👇 */
type NewPerson = {
  [key in Kind]: Person
};

Extract

👺 從 T 中提取 U

  • 代碼實現
type Extract<T, U> = T extends U ? T : never;
  • Demo
type Student = '二狗子' | '如花';
type Teacher = '黃刀小五' | '二狗子';
type Trainee = Extract<Student, Teacher>; // 輸出 '二狗子'

/* Extract<Student, Teacher> 至關於👇 */
'二狗子' in Teacher -> '二狗子'
'如花' in Teacher -> never
'二狗子' | never = '二狗子'

Exclude

👺 從 T 中排除 U

  • 代碼實現
type Exclude<T, U> = T extends U ? never : T;
  • Demo
type Student = '二狗子' | '如花';
type Teacher = '黃刀小五' | '二狗子';
type OnlyStudent = Exclude<Student, Teacher>; // 輸出 '如花'

/* Exclude<Student, Teacher> 至關於👇 */
'二狗子' in Teacher -> never
'如花' in Teacher -> '如花'
'如花' | never = '如花'

Pick

👺 挑選對象中的部分屬性

  • 代碼實現
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
}
  • Demo
interface Person {
  readonly name: string;
  age: number;
  desc?: string;
}
let person: Pick<Person, 'age' | 'desc'> = {
  age: 18,
};

/* Pick<Person, 'age' | 'desc'> 至關於👇 */
interface Person {
  age: number;
  desc?: string;
}

Omit

👺 忽略對象中的部分屬性

  • 代碼實現
type Omit<T, K> = Pick<T, Exclude<keyof T, K>>;
  • Demo
interface Person {
  readonly name: string;
  age: number;
  desc?: string;
}
let person: Omit<Person, 'name'> = {
  age: 18,
};

/* Omit<Person, 'name'>至關於👇 */
interface Person {
  age: number;
  desc?: string;
}

ReturnType

👺 獲取方法的返回值類型

  • 代碼實現
type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any;

-> infer 配合 extends 使用, 用於推斷函數的返回值類型

  • Demo
const getName = (name: string) => name;
type ReturnGetName = ReturnType<typeof getName>; // string

Parameters

👺 獲取方法的參數類型

  • 代碼實現
type Parameters<T> = T extends (
  ...args: infer P
) => any ? P : never;

-> 這裏 infer 用於推斷函數的參數類型, Parameters 返回格式爲元組.

  • Demo
const getName = (name: string) => name;
type ParamGetName = Parameters<typeof getName>; // [name: string]

NonNullable

👺 排除 nullundefined 類型

  • 代碼實現
type NonNullable<T> = T extends null | undefined ? never : T;
  • Demo
type NewPerson = Person | null;
let person: NonNullable<NewPerson> = null; // ❎ error (不能將類型「null」分配給類型「Person」)

結束語

結合文章內容, 你們可根據實際需求封裝更多的語法糖, 便於在項目中使用. 若想更細緻的學習 TS, 這裏推薦一個博主 阿寶哥, 其發表了不少關於 TS 的文章, 並針對 TS 的某一特徵作了詳細的講解.

參考🔗連接

【TypeScript 文檔】

【FESKY】 結合實例學習 Typescript

【劉哇勇】 TypeScript infer 關鍵字

相關文章
相關標籤/搜索