重溫Typescript

ethanYin發表於2023-04-10

對TS有些生疏了,想著來總結一波,可作為日後工作中的快速參考手冊。

TypeScript具有型別系統,且是JavaScript的超集。

它可以編譯成普通的JavaScript程式碼。 TypeScript支援任意瀏覽器,任意環境,任意系統並且是開源的。

TS是JS的超集,我的理解是:JS + 型別系統 = TS,對於熟悉JS的同學來說,TS的學習成本很低!

專案引入TS好處也很明顯:

  • 讓程式更加健壯,更易維護。
  • 在編譯器中的型別提示讓一些低階錯誤在編譯階段就被發現。
  • 避免一些無效的溝通。
??聽說你很會Ts? 來,讓我看看??!!

初學者建議移步 【TS中文網】 進行學習

若行文有誤,歡迎指出??????

一些準備工作

這些工作更多的是為了測試ts特性。
當然這不是必須的,也可以到 Typescript練習場 去練習和測試。

安裝 typescriptts-node 。前者是安裝一個ts環境,將編譯ts檔案為js,方便檢視其編譯情況,後者支援終端執行ts檔案。

npm install -g  typescript # 全域性安裝 ts

tsc -v # 檢視安裝的版本
# Version 5.0.3

npm install -g ts-node # 全域性安裝 ts-node

ts-node -v
# v10.9.1

新建一個名 summary 的目錄,進入目錄新建 helloTS.ts 檔案。

mkdir summary
cd summary
touch helloTs.ts

上面步驟進行順利的話,此時可以初始化目錄、編譯檔案、執行檔案了。

helloTs.ts裡寫下一點想法

const hello: string = '你好,ts, 我頂你個肺啊,哈哈哈'

終端

tsc --init # 初始化,同目錄下會生成 tsconfig.json 檔案

tsc helloTs.ts # 編譯,初次執行會在同目錄下生成 helloTs.js 檔案,這裡面就是編譯的結果

ts-node helloTs.ts # 執行 helloTs.ts 檔案,有點像 node xxx.js

tsconfig.json 檔案定義著 會對哪些檔案進行編譯,如何編譯(細則,約束),見最後。

基礎型別

變數型別宣告 是 冒號+ 型別 對一個變數進行型別宣告的(廢話。

js的八大型別

let str: string = "ethan";
let num: number = 24;
let bool: boolean = false;
let und: undefined = undefined;
let nul: null = null;
let obj: object = {x: 1};
let big: bigint = 100n;
let sym: symbol = Symbol("me"); 

null && undefined

這兩個值稍微特殊些,null,undefined 是所有型別的子型別,即可以賦值給任何型別

let obj:object ={};
obj = null;
obj= undefined;

const und: undefined = undefined;
const nul: null = null;
let str: string = nul;
let num: number = und;

如果開啟嚴格模式:在 tsconfig.json 指定了 "strict": true,"strictNullChecks":true,執行會拋錯,在編譯器裡就會有提示。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-xLxjyYGH-1680958995419)(/Users/ethanyin/Library/Application%20Support/marktext/images/2023-04-07-17-32-04-image.png)]

let aa: string = nul;// ❌不能將型別“null”分配給型別“string”
let b: number = und;// ❌不能將型別“undefined”分配給型別“number”。

空值(void

void 意思就是無效的, 一般作用在函式上,告訴別人這個函式沒有返回值

function sayHello(): void {
  console.log("hello ts");
}

需要注意 null 賦值給 void 值在嚴格模式下會拋錯

let c:void = undefined // 編譯正確
let d:void = null // 嚴格模式下: ❌不能將型別“null”分配給型別“void”

never

never型別表示的是那些永不存在的值的型別。 例如,never 型別是那些總是會丟擲異常或根本就不會有返回值的函式表示式或箭頭函式表示式的返回值型別。

// 返回never的函式必須存在無法達到的終點
function error(message: string): never {
    throw new Error(message);
}

// 推斷的返回值型別為never
function fail() {
    return error("Something failed");
}

// 返回never的函式必須存在無法達到的終點
function infiniteLoop(): never {
    while (true) {
    }
}

列舉(enum

使用列舉我們可以定義一些有名字的數字常量。 列舉透過 enum 關鍵字來定義。

// 不賦值,預設從0開始~~
// 賦值後即從該值往後 依次累加
enum Anmimal {
  Cat,// 0
  Dog = 2,
  Num = Dog * 2, // 4
  Bird // 5,
  G = '123'.length // 計算成員
}
const cat: Anmimal = Anmimal.Cat;
const dog: Anmimal = Anmimal.Dog;
const num: Anmimal = Anmimal.Num;
const bird: Anmimal = Anmimal.Bird;
console.log(cat, dog, num, bird) // 0 2 4 5

異構列舉(Heterogeneous enums),指列舉值同時出現字串和數字,儘量避免。

enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

常量列舉(constant enum),使用const關鍵字修飾的常量列舉在編譯後會被刪除

const enum Color {
  RED,
  PINK,
  BLUE,
}

const color: Color[] = [Color.RED, Color.PINK, Color.BLUE];
console.log(color); //[0, 1, 2]

//編譯後
var color = [0 /* Color.RED */, 1 /* Color.PINK */, 2 /* Color.BLUE */];
console.log(color); //[0, 1, 2]

執行時列舉(Enums at runtime)

enum E { X, Y, Z }
function f(obj: { X: number }) {
  return obj.X;
}
// Works, since 'E' has a property named 'X' which is a number.
console.log(f(E)); // 0

編譯時列舉(Enums at compile time)

enum LogLevel { ERROR, WARN, INFO, DEBUG, }
type LogLevelStrings = keyof typeof LogLevel; // 'ERROR' | 'WARN' | 'INFO' | 'DEBUG'
function printImportant(key: LogLevelStrings, message: string) {
  const num = LogLevel[key];
  if (num <= LogLevel.WARN) {
    console.log("Log level key is:", key); // Log level key is: ERROR
    console.log("Log level value is:", num); // Log level value is: 0
    console.log("Log level message is:", message); // Log level message is:This is a message
  }
}
printImportant("ERROR", "This is a message");

任意值(any

眾所周知,TS的any大法無所不能,哈哈哈~~~~(玩笑

當在程式設計階段給還不清楚型別的變數指定一個型別。 這些值可能來自於動態的內容,比如來自使用者輸入或第三方程式碼庫,那就需要使用any了。

any 型別可賦值給任何型別,並接受任何型別賦值。

let notSure: any = 4;
notSure = "maybe a string instead";
notSure = false; // 可接受二次型別賦值
let str:string = notSure;// any可給任意型別賦值

let obj: any = 4;
obj.ifItExists(); // ifItExists 方法也許在執行時存在
obj.toFixed(); // toFixed 存在 (但是編譯時不檢查)

let list: any[] = [1, true, "free"];

unknown

unknownany 一樣,所有型別都可以分配給 unknown

let value: unknown = 1;
value = "hello"; 
value = false; 

any 不同的是,unknown 的值只能賦值給 unknown any

let unv: unknown = 1;
let anyv: any = unv;
let str: string = unv; // ❌不能將型別“unknown”分配給型別“string”。

物件型別

在JavaScript中,我們分組和傳遞資料的基本方式是透過物件。在TypeScript中,我們透過物件型別(泛指)來表示這些型別。

這裡的物件很抽象,記得剛學java時,老師說過:“萬物皆可為物件~~”。你懂我意思吧?。

陣列

陣列兩種申明方式

const arr: number[] = [1,2];
const arr1:Array<number> = [1,2]; 

物件型別

這個物件就是實實在在的,具體的,物件分為 objectObject{}

object 僅僅指一個物件,不接受其他基礎值的賦值,包括(null, undefined)。

let object: object;
// object = 1; // 報錯:不能將型別“number”分配給型別“object”
// object = "a"; // 報錯:不能將型別“string”分配給型別“object”。
object = {}; // 編譯正確

Object 代表所有擁有 toStringhasOwnProperty 方法的型別,所以所有原始型別、非原始型別都可以賦給 Object(嚴格模式下 null 和 undefined 不可以)。{}同樣,可以認為是Object的簡寫

let bigObj: Object;
bigObj = 1;
bigObj = "a";

let normalObj: {};
normalObj = 1;
normalObj = "a";

let object: object;
normalObj = {};

{} 和大 Object可以互相代替,用來表示原始型別(null、undefined 除外)和非原始型別;而小 object 則表示非原始型別。object 更像是 Object的子集。

另外注意,除了 Oject大小寫可以相互賦值,而其他基礎型別(number、string、boolean、symbol)大寫賦值給小寫會拋錯,且沒有意義。

❌ Don’t ever use the types Number, String, Boolean, Symbol, or Object These types refer to non-primitive boxed objects that are almost never used appropriately in JavaScript code.

----
https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#general-types

元祖(Tuple

元組型別允許表示一個 已知元素數量和型別 的陣列,各元素的型別不必相同。可以理解為陣列的擴充

const tuple: [string, number] = ['哈哈', 1];

type Either2dOr3d = [number, number, number?];// number可選

// ... 表示0或多個 boolean 值 的元素
type StringNumberBooleans = [string, number, ...boolean[]];
type StringBooleansNumber = [string, ...boolean[], number];
type BooleansStringNumber = [...boolean[], string, number];

// 只讀元祖
const roTuple: readonly [string, number] = ['哈哈', 1]

擴充套件型別(Extending Types

對某一個型別 使用 extends 進行擴充套件

interface BasicAddress {
  name?: string;
  street: string;
  city: string;
  country: string;
  postalCode: string;
}

interface AddressWithUnit extends BasicAddress {
  unit: string;
}

const liveAdress: AddressWithUnit = {
  unit: "ts",
  name: "ethan",
  street: "secret",
  city: "secret",
  country: "china",
  postalCode: "dont know",
}

交叉型別(Intersection Types

交叉型別 就是跟 聯合型別(Union Types,後面提到)相反,用 & 運算子表示,交叉型別就是兩個型別必須存在

interface PersonA{ // interface 為介面
  name: string,
  age: number,
  birth?: Date; // 生日:YYYY-MM-DD
  hobby?: "football" | "tennis"; // 愛好 只能為 "football" 或 "tennis"
}

interface PersonB {
  name: string,
  gender: string
}

type PersonAWithBirthDate = PersonA & { birth: Date };

let person: PersonA & PersonB = { 
    name: "ethan",
    age: 18,
    gender: "男"
};

只讀陣列(ReadonlyArray Type

此型別的陣列不能被改變

const roArray: ReadonlyArray<string> = ["red", "green", "blue"];
roArray[0] = 'change' // ❌型別“readonly string[]”中的索引簽名僅允許讀取
roArray.push('grey'); // ❌型別“readonly string[]”上不存在屬性“push”。

以上情況 與只讀修飾符 的效果一樣

const roArray: readonly string[] = ["red", "green", "blue"];

最後需要注意的一點是,與只讀屬性修飾符不同,可賦值性在常規陣列和只讀陣列之間不是雙向(不可分配/賦值)的。

let xx: readonly string[] = [];
let yy: string[] = [];
 
xx = yy; // ok
yy = xx; // ❌型別 "readonly string[]" 為 "readonly",不能分配給可變型別 "string[]"。

roArray = yy; // 因為const定義的。❌型別 "readonly string[]" 為 "readonly",不能分配給可變型別 "string[]"
yy = roArray; // ❌型別 "readonly string[]" 為 "readonly",不能分配給可變型別 "string[]"。

函式

js 的一等公民,使用場景太多了

函式:支援給引數、返回值定義型別;預設引數依然適用 ;?表示y是可選引數

function func(x: number, def: boolean = false, y?: number): number {
  return y ? x + y : x; // 對於可選引數需要做判斷
}

// 介面定義函式,後面講
interface Add {
  (x: number, y: number): number;
}

// 宣告一個箭頭函式,=> 後面number 約定的是返回值型別
let Add: (baseValue: number, increment: number) => number =
    function(x: number, y: number): number { return x + y; };

支援剩餘引數定義型別

function restFunc(...numbers: number[]): number {
  let sum = 0;
  for (let i = 0; i < numbers.length; i++) {
    sum += numbers[i];
  }
  return sum;
}

函式過載,函式過載真正執行的是同名函式最後定義的函式體,在最後一個函式體定義之前全都屬於函式型別定義 ,不能寫具體的函式實現方法,只能定義型別

function add(x: number, y: number): number;
function add(x: string, y: string): string;
function add(x: any, y: any): any {
  return x + y;
}

約定了add 接收的引數為 一旦有數字或字串,兩個引數必須同為數字或字串,且返回值也應該為數字或字串。相當於約定了引數的一致性(入參,出參),當然這裡只是演示,並不是說他的作用只有這樣,畢竟入參和出參自己定義,但應該是一個強繫結的。

型別推斷

對某個變數直接賦值,那麼改變數的型別就會被推斷為該值的型別

let number = 1;// 被推斷為number型別

如果定義的時候沒有賦值,不管之後有沒有賦值,都會被推斷成 any 型別而完全不被型別檢查.

let value;
value = 'ethan';
value = 7;

型別斷言

對於某些資料,我比TS更加了解它的型別,那麼這時候型別斷言就適用。

型別斷言: 手動指定一個值的型別

基本語法

支援兩種方式

// 方式1
let str: any = "to be or not to be";
let strLength: number = (<string>str).length;
// 方式2
let str1: any = "to be or not to be";
let strLength1: number = (str1 as string).length;

非空斷言

對於多型別的資料,某個場景下無法根據上下文確定其型別,那麼非空斷言可以支援排除 nullundefined 值,避免使用時不必要的判斷。語法是 一個字尾表達符號 !(英文感嘆號)

// 場景1
let value: null | undefined | string;
value!.toString();
value.toString(); // ❌value可能為 “null” 或“未定義”。

// 場景2
type funcType = () => number
function myFunc(func: funcType | undefined) {
  const num1 = func(); // “func”可能為“未定義”
  const num2 = func!();
}

確定賦值斷言

某個變數被定義後,未賦值前使用就會報錯,這時候可以利用確定賦值斷言解決。相當於告訴TS,我後面會賦值的,你別瞎操心了,哈哈。

let value:number;
// 以下語句未賦值使用就會拋錯
console.log(value); // ❌在賦值前使用了變數“value”。

//使用賦值斷言
let value!:number;
console.log(value); // undefined

聯合型別(Union Types

將二種及以上多種型別用 | 隔開的型別稱為聯合型別

let union: string | number; // union 支援賦值 string 或 number

type U = string[] | string; // 支援字串陣列或字串

條件型別(Conditional Types

簡單理解就像 js 的 三目運算子.結合程式碼很好理解。

SomeType extends OtherType ? TrueType : FalseType;

基本使用

interface Animal {
  live(): void;
}
interface Dog extends Animal {
  woof(): void;
}
 
type Example1 = Dog extends Animal ? number : string; // type Example1 = number
 
type Example2 = RegExp extends Animal ? number : string; // type Example2 = string

在某些情況下,可以避免寫複雜的過載函式。下面是一個過載函式示例

interface IdLabel {
  id: number;
}
interface NameLabel {
  name: string;
}
 
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel;
function createLabel(nameOrId: string | number): IdLabel | NameLabel {
  throw "unimplemented";
}

利用條件型別改一下

type nameOrId<T extends number | string> = T extends number
  ? IdLabel
  : NameLabel;
function createLabel<T extends number | number>(idOrName: T): NameOrId {
    thorw "unimplemented";
}

可以看到 nameOrId 做到了 入參型別不同,出參型別自動判斷。很明顯在只有兩種條件的情況下很便捷。

泛型限制

與泛型結合使用時能更好的約束泛型。

type MessageOf<T> = T extends { message: unknown } ? T["message"] : never;

type Flatten<T> = T extends any[] ? T[number] : T;

分配條件型別

當我們向條件型別傳入聯合型別,如果沒有使用 [] 會出現對映聯合型別的每個成員型別。

type ToArray<T> = T extends any ? T[] : never;
type ToArray2<T> = [T] extends [any] ? T[] : never;

type StrArrOrNumArr = ToArray<string | number>; // string[] | number[]
type StrArrOrNumArr2 = ToArray2<string | number>; // (string | number)[]

型別別名

型別別名用 type 來給一個型別起個新名字。它只是起了一個新名字,並沒有建立新型別。型別別名常用於聯合型別。

type personType = {
   name: string,
   age: number  
}
let Ethan: personType;

type count = number | number[];
function hello(value: count) {}

型別保護(Type Guards

型別保護是執行時檢查某些值,確保某些值在所要型別的範圍內。(結合程式碼示例更方便理解

型別保護有四種方式

1,is(自定義型別保護)

function getLength(arg: any): pet is string {
    return arg.length;
}

2,typeof, 利用判斷去做

function getType(val: any) {
  if (typeof val === "number") return 'number'
  if (typeof val === "string") return 'string'
  return '啥也不是'
}

3,instanceof, instanceof型別保護*是透過建構函式來細化型別的一種方式

function creatDate(date: Date | string){
  console.log(date)
  if(date instanceof Date){
      date.getDate()
  }else {
      return new Date(date)
  }
}

4,in

type Fish = { swim: () => void };
type Bird = { fly: () => void };

function move(pet: Fish | Bird) {
  if ("swim" in pet) { // 如果pet裡存在 swim方法則為Fish,就遊起來
    return pet.swim();
  }
  return pet.fly(); // 否則一定為Bird,就飛起來
}

介面(interface

這,是個大玩意兒。

在TypeScript裡,介面的作用就是為這些型別命名和為你的程式碼或第三方程式碼定義契約。

介面命名一般首字母大寫。

基本用法

// 此介面囊括了介面的大部分特性
interface InterA{
  prop1: string,
  prop2?: number, // 可選引數
  readonly prop3: boolean //只讀引數
  readArr: ReadonlyArray<number> //只讀陣列,一經定義不能被修改
  birth?: Date; // 生日(可選):YYYY-MM-DD
  hobby?: "football" | "tennis"; // 愛好(可選), 只能為 "football" 或 "tennis"
  // 以下使用來擴充套件未知欄位的,也就是說此介面除了以上欄位還有許多不確定的欄位。
  [prop: string | number]: any; // 索引簽名, prop欄位必須是 string型別 or number型別。
}

擴充套件

介面支援 extends 擴充套件更多的屬性

interface InterA {
  // props
}
// InterB 在 InterA 的基礎上擴充套件了 propb 屬性
interface InterB extends InterA {
  propb: string
}

擴充套件多型別

interface Colorful {
  color: string;
}
interface Circle {
  radius: number;
}

interface ColorfulCircle extends Colorful, Circle {}

const cc: ColorfulCircle = {
  color: "red",
  radius: 42,
};

介面(interface)和型別(type)的區別

概念上,interface是一種具體的型別,而type僅是型別的一種別名。

大多數情況下,此兩者使用起來差不多。都可以用來描述物件/函式的型別,都可以擴充套件

這裡有一些區別

1,語法

// 定義
type MyTYpe = {
  name: string;
  say(): void;
}

interface MyInterface {
  name: string;
  say(): void;
}

// 擴充套件
type MyType2 = MyType & {
  sex:string;
}
let person:MyInterface2 = {
  name:'ethan',
  sex:'男',
  say(): void { console.log("hello ts") }
}

interface MyInterface2 extends MyInterface {
  sex: string;
}
let person1:MyInterface2 = {
  name:'ethan',
  sex:'男',
  say(): void { console.log("hello ts") }
}

當然,也支援互相擴充套件

interface MyInterface3 {
  sex: string;
}

type mixType = MyType & MyInterface3
interface mixInterface extends MyType { sex: string }

上面可能是小不同,下面的不同可能讓它兩在各自場景能各盡其用:

  • type 可以宣告基本資料型別別名/聯合型別/元組等,而 interface 不行
// 基本型別別名
type UserName = string;
type UserName = string | number;
// 聯合型別
type Animal = Pig | Dog | Cat;
type List = [string, boolean, number];
  • interface 能夠合併宣告,而type 不行
interface Person {
  name: string
}
interface Person {
  age: number
}
// 此時Person同時具有name和age屬性

泛型(Generics

泛型是指在定義函式、介面或類的時候,不預先指定具體的型別,而在使用的時候再指定型別的一種特性。

假設一個場景:一個函式需要入參出參都是 string。可能會這樣寫

function func(arg:string):string { return arg }

現在需求改了,除了支援 string,還得支援 number,你可能會這樣寫

function func(arg:string | number):string | number { return arg }

某日,需求又至矣,修修改改無窮盡也??吾心有一馬奔騰不止。。。
泛型就是解決這樣的問題

基本使用

function func<T>(arg:T):T  {
  return arg;
}
func<string | number>('哈哈哈');
func('哈哈哈');// 不指定型別,ts會自動推斷

let myIdentity: <U>(arg: U) => U = identity;

泛型中的 T 就像一個佔位符、或者說一個變數,在使用的時候可以把定義的型別像引數一樣傳入,它可以原封不動地輸出

Array 自身也是個泛型。

interface Array<Type> {
  length: number;
  pop(): Type | undefined;
  push(...items: Type[]): number;
  // ...
}

多個引數

function func<T, U>(arg:[T,U]):[T,U] {
  return arg;
}
func("ethan", 18);// ["ethan", 18]

泛型約束

即預先指定該泛型滿足某些條件

示例1 基本用法

// 使用extends約束泛型
interface Lengthwise {
  length: number;
}
function getLength1<T extends Lengthwise>(arg:T):T  {
  console.log(arg.length); 
  return arg;
}

const str = getLength('ethan')
const arr = getLength([1,2,3])
const obj = getLength({ length: 1 })
const num = getLength1(1) // ❌型別“number”的引數不能賦給型別“Lengthwise”的引數。

示例2 泛型約束中使用型別引數

function getProperty<T, K extends keyof T>(obj: T, key: K) {
    return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m"); // ❌型別“"m"”的引數不能賦給型別“"a" | "b" | "c" | "d"”的引數泛型介面

泛型介面

演示 介面與泛型 如何搭配

interface GenericIdentityFn<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;

泛型類

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };

泛型別名

type Cart<T> = { list: T[] } | T[];
let c1: Cart<string> = { list: ["1"] };
let c2: Cart<number> = [1];

泛型工具型別

1,typeof,typeof 的主要用途是在型別上下文中獲取變數或者屬性的型別

let p1 = { name: "ethan", age: 18, isAdult: true };
type People = typeof p1; // 返回的型別一般用type承接取個別名
function getName(p: People): string {
  return p.name;
}
getName(p1);

// typeof p1 編譯後
type People = {
  name: string,
  age: number,
  isAdult: boolean,
};

如果p1為巢狀物件也適用。

2, keyof,此運算子是獲取某種型別的所有鍵,返回的是聯合型別。

interface Person {
  name: string;
  age: number;
}

type K1 = keyof Person; // "name" | "age"
type K2 = keyof Person[]; // "length" | "toString" | "pop" | "push" | "concat" | "join" 
type K3 = keyof { [x: string]: Person };  // string | number

// 也支援基礎型別
let K1: keyof boolean; // let K1: "valueOf"
let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
let K3: keyof symbol; // let K1: "valueOf"

3, in,用以遍歷列舉型別

type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

4, infer,在條件型別語句中,宣告型別變數以使用它。

type ReturnType<T> = T extends (
  ...args: any[]
) => infer R ? R : any; // R承載了返回值的型別

5,extends,為泛型增加約束, 參考之前泛型。

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}

6,[Type][key], 索引訪問運算子

interface Person {
  name: string;
  age: number;
}

type Name = Person["name"]; // string

實用程式型別(Utility Types

1,Required,將型別的屬性變成必選

Required<Type>
interface Person {
    name?: string,
    age?: number
}
const person: Required<Person> = {
    name: "ethan",
    age: 18
}

2,Partial,與 Required 相反,將所有屬性轉換為可選屬性

Partial<Type>
interface Person {
    name: string,
    age: number
}
type User = Partial<Person>
const shuge: User = {// 輸入Person的某些屬性
  name:'ethan'
}

3,Extract,相當於取交集(參1∩參2)

Extract<Type, Union>
type T01 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T11 = Extract<string | number | (() => void), Function>; // () =>void

4,Exclude,相當於取差集(參1 - 參1∩參2)

Exclude<UnionType, ExcludedMembers>
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number

5,Readonly,將陣列或物件的所有屬性值轉換為只讀。

Readonly<Type>
interface Todo {
  title: string;
}
const todo: Readonly<Todo> = {
  title: "Delete inactive users",
}
todo.title = "Hello"; // ❌無法為“title”賦值,因為它是隻讀屬性。

6,Record, 將參2的型別一一賦給參1的key

Record<Keys, Type>,等同於

type newType = {
  [key keyof keys]: Type
}
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" },
};

7,Pick,從某些型別裡面挑一些屬性出來,方便複用

Pick<Type, Keys>
type Person = {
  name: string;
  age:number;
  gender:string
}

type P1 = Pick<Person, "name" | "age">; // { name: string; age: number; }

8,Omit,與Pick相反,從某些型別裡面剔除一些屬性出來,方便複用

Omit<Type, Keys>
interface Person {
  name: string,
  age: number,
  gender: string
}
type P1 = Omit<Person, "age" | "gender"> // { name: string }

9,ReturnType,獲取函式返回值型別

ReturnType<Type>
type T0 = ReturnType<() => string>; // string

function f1(): { a: number; b: string };
type T1 = ReturnType<typeof f1>; // { a: number; b: string }

type T2 = ReturnType<<T extends U, U extends number[]>() => T>;// number[]

10,NonNullable,去除 null 和 undefined 型別

Parameters<Type>
type P1 = NonNullable<string | number | undefined>; // string | number
type P2 = NonNullable<string[] | null | undefined>; // string[]

11,Paramenters, 獲取函式型別的引數型別,結果是一個元祖(Tuple

Parameters<Type>
type T0 = Parameters<() => string>; // []

function f1(arg: { a: number; b: string }): void;
type T1 = Parameters<typeof f1>; // [arg: { a: number; b: string }]

12, InstanceType, 獲取 建構函式的例項型別

InstanceType<Type>
class C {
  x = 0;
  y = 0;
}
type T0 = InstanceType<typeof C>; // C
type T1 = InstanceType<any>; // any

13, Awaited,比較新,獲取非同步函式中的await或Promises上的.then()方法 返回的型別.

Awaited<Type> | Released: 4.5
type A = Awaited<Promise<string>>; // string

type B = Awaited<Promise<Promise<number>>>; // number

type C = Awaited<boolean | Promise<number>>; // number | boolean

我的理解是為了更好的處理 async 函式內 await 的結果型別。

async function foo<T>(x: T) {
    const y: Awaited<T> = await x;
    // const y: Awaited<T>
    // const y: T before TS4.5
}

參考
What is the Awaited Type in TypeScript - Stack Overflow

https://www.typescriptlang.org/play

14, 大小寫轉換

Uppercase<StringType> | 全大寫

Lowercase<StringType> | 全小寫

Capitalize<StringType> | 首字母寫

Uncapitalize<StringType> | 非首字母大寫

很好理解,為了更加精確的規定字串型別的值,值也是一種型別嘛。

type Greeting = "Hello, world";
type ShoutyGreeting = Uppercase<Greeting>;
// type ShoutyGreeting = "HELLO, WORLD"
const helloStr: ShoutyGreeting = "HELLO, WORLD";
const helloStr: ShoutyGreeting = "hello";// ❌不能將型別“"hello"”分配給型別“"HELLO, WORLD"”。

type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`;
type MainID = ASCIICacheKey<"my_app">;
// type MainID = "ID-MY_APP"

type Greeting = "Hello, world";
type QuietGreeting = Lowercase<Greeting>;
// type QuietGreeting = "hello, world"

type LowercaseGreeting = "hello, world";
type Greeting = Capitalize<LowercaseGreeting>;
// type Greeting = "Hello, world"

type UppercaseGreeting = "HELLO WORLD";
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
// type UncomfortableGreeting = "hELLO WORLD"

還有更多的實用工具類,不一一例舉了 ,可見 官網

型別相容性

這兩天在 思否 上看一些ts相關的問題,看到一個有趣的問題
javascript - 虛心請教關於TypeScript的問題? - SegmentFault 思否

有個回答講到了型別相容性,很有意思推薦大家去看看。我總結為三點(僅從回答)

  1. 當傳入一個物件時,如果該物件包含了目標型別中不存在的屬性,TypeScript 會忽略這些屬性而不會報錯
  2. 當傳入一個物件時,必須該物件包含目標型別屬性,這是型別相容性的前提
  3. 在約定了函式引數型別時,在呼叫時傳入具體的值而非變數(情況1)。那就會觸發ts型別初始化,在型別不匹配的情況下從而報錯!

示例

interface Pet {
  name: string; // name是否可選不影響結果
}
function greet(pet: Pet) {
  console.log("Hello, " + pet.name);
}

// 情況1
let dog = { name: "Lassie", owner: "Rudd Weatherwax" };
greet(dog); // OK

// 情況2
let anotherDog = { owner: "Rudd Weatherwax" };
greet(anotherDog); // ❌ 如下:
// 型別“{ owner: string; }”的引數不能賦給型別“Pet”的引數。
// 型別 "{ owner: string; }" 中缺少屬性 "name",但型別 "Pet" 中需要該屬性。

// 情況3
greet({ name: "Lassie", owner: "Rudd Weatherwax" }); // ❌ 如下:
// 型別“{ name: string; owner: string; }”的引數不能賦給型別“Pet”的引數。
// 物件字面量只能指定已知屬性,並且“owner”不在型別“Pet”中。

詳見 https://www.typescriptlang.org/docs/handbook/type-compatibility.html

配置檔案(tsconfig.json

主要欄位

  • extends - 繼承配置, 例 "extends": "./configs/base",
  • files - 設定要編譯的檔案的名稱;
  • include - 設定需要進行編譯的檔案,支援路徑模式匹配;
  • exclude - 設定無需進行編譯的檔案,支援路徑模式匹配;即使include包含了,但不能排除files的檔案
  • compilerOptions - 設定與編譯流程相關的選項,初始化的時候只會有此欄位。

compilerOptions

{
  "compilerOptions": {

    /* 基本選項 */
    "target": "es5",           // 指定 ECMAScript 目標版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",      // 指定使用模組: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                 // 指定要包含在編譯中的庫檔案
    "allowJs": true,           // 允許編譯 javascript 檔案
    "checkJs": true,           // 報告 javascript 檔案中的錯誤
    "jsx": "preserve",         // 指定 jsx 程式碼的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,       // 生成相應的 '.d.ts' 檔案
    "sourceMap": true,         // 生成相應的 '.map' 檔案
    "outFile": "./",           // 將輸出檔案合併為一個檔案
    "outDir": "./",            // 指定輸出目錄
    "rootDir": "./",           // 用來控制輸出目錄結構 --outDir.
    "removeComments": true,    // 刪除編譯後的所有的註釋
    "noEmit": true,            // 不生成輸出檔案
    "importHelpers": true,     // 從 tslib 匯入輔助工具函式
    "isolatedModules": true,   // 將每個檔案做為單獨的模組 (與 'ts.transpileModule' 類似).

    /* 嚴格的型別檢查選項 */
    "strict": true,            // 啟用所有嚴格型別檢查選項
    "noImplicitAny": true,     // 在表示式和宣告上有隱含的 any型別時報錯
    "strictNullChecks": true,  // 啟用嚴格的 null 檢查
    "noImplicitThis": true,    // 當 this 表示式值為 any 型別的時候,生成一個錯誤
    "alwaysStrict": true,      // 以嚴格模式檢查每個模組,並在每個檔案里加入 'use strict'

    /* 額外的檢查 */
    "noUnusedLocals": true,                // 有未使用的變數時,丟擲錯誤
    "noUnusedParameters": true,            // 有未使用的引數時,丟擲錯誤
    "noImplicitReturns": true,             // 並不是所有函式里的程式碼都有返回值時,丟擲錯誤
    "noFallthroughCasesInSwitch": true,    // 報告 switch 語句的 fallthrough 錯誤。(即,不允許 switch 的 case 語句貫穿)

    /* 模組解析選項 */
    "moduleResolution": "node",            // 選擇模組解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用於解析非相對模組名稱的基目錄
    "paths": {},                           // 模組名到基於 baseUrl 的路徑對映的列表
    "rootDirs": [],                        // 根資料夾列表,其組合內容表示專案執行時的結構內容
    "typeRoots": [],                       // 包含型別宣告的檔案列表
    "types": [],                           // 需要包含的型別宣告檔名列表
    "allowSyntheticDefaultImports": true,  // 允許從沒有設定預設匯出的模組中預設匯入。

    /* Source Map Options */
    "sourceRoot": "./",          // 指定偵錯程式應該找到 TypeScript 檔案而不是原始檔的位置
    "mapRoot": "./",             // 指定偵錯程式應該找到對映檔案而不是生成檔案的位置
    "inlineSourceMap": true,     // 生成單個 soucemaps 檔案,而不是將 sourcemaps 生成不同的檔案
    "inlineSources": true,       // 將程式碼與 sourcemaps 生成到一個檔案中,要求同時設定了 --inlineSourceMap 或 --sourceMap 屬性

    /* 其他選項 */
    "experimentalDecorators": true,        // 啟用裝飾器
    "emitDecoratorMetadata": true          // 為裝飾器提供後設資料的支援
  }
}

結尾

github 上面有個 型別體操 的專案,適合練習,進階。

參考了一些網上的部落格,甚至有些例子是直接copy, 非常感謝???。

5分鐘瞭解 TypeScript - TypeScript 中文手冊

https://www.typescriptlang.org/

2022年了,我才開始學 typescript ,晚嗎?(7.5k字總結) - 掘金

2022 typescript史上最強學習入門文章(2w字) - 掘金

「1.9W字總結」一份通俗易懂的 TS 教程,入門 + 實戰! - 掘金

TypeScript 型別操作 - 掘金

相關文章