《TypeScript入門與實戰》筆記

TypeScript 入門與實戰

  • tsconfig.json 編譯配置文件 compilerOptions

    • 啟用 --strict 編譯選項,開啟嚴格模式
    • 啟用 --strictNullChecks,undefined 值只能夠賦值給 undefined 類型,null 值只能夠賦值給 null 類型,實際上這種表述不完全準確。因為在該模式下,undefined 值和 null 值允許賦值給頂端類型,同時 undefined 值也允許賦值給 void 類型。
    • 啟用 --noImplicitAny,禁用隱式 any 類型轉換
    • 啟用 --noImplicitThis,this 不能隨意.任意值
    • 啟用 --strictPropertyInitialization,類的成員變量初始化檢查,需要與 --strictNullChecks 同時使用
    • 啟用 --strictFunctionTypes,嚴格函數類型檢查,false 時 對應位置上的參數只要存在子類型關系即可,而不強調誰是誰的子類型
    • 啟用 --noStrictGenericChecks,非嚴格泛型函數類型檢查,即忽略函數的參數類型
    • --importsNotUsedAsValues: remove|preserve|error,如何處理 import type,刪除/保留/強制 import type
    • moduleResolution: Classic|Node,默認與--module相關: --module CommonJS --moduleResolution Node, --module ES6 --moduleResolution Classic
  • //#region //#endregion 定義了代碼折疊區域的起止位置

  • es6 規范中定義的 7 種數據類型:Undefined、Null、Boolean、String、Symbol、Number、Object

    • unique symbol:ts 中只允許用 const 或 readonly 來定義
    • unique symbol:ts 中只允許用 Symbol()或 Symbol.for()初始化(“確保”其唯一性)
    • unique symbol:ts 中將 Symbol.for('same')賦值給兩個變量,編譯器會認為它們是兩個不同的值,即使他們相同。
  • 枚舉:無初始值,從前一個枚舉成員的值+1(如果是首個為 0)

    • 變量:數值型枚舉 = number ??; 變量:字符串枚舉 = string ?
    • 在帶有字符串成員的枚舉中不允許使用計算值
    • 枚舉成員映射(僅數值型枚舉):Bool[Bool.False] // 'False'
    • const enum 編譯后會被替換成字面量,非const會被轉換成對象調用
  • any 與 unknown 的區別:any 為放棄類型檢查,unknown 為未知類型。

    • any 可以給任意類型變量賦值,nuknown 不可。
    • any 可以進行任意操作,nuknown 幾乎什么不能干(字符串、對象操作、數學運算等);
  • 數組

    • const arr: (string | number)[] = [0, 'a']const arr: string | number[] = 'a' 不同
    • const arr: string[] = ['0', 'a']const arr: [string] = ['a'] 不同
    • 只讀數組:arr: ReadonlyArray<string>arr: readonly string[]。不能 arr: readonly Array<string>
      • 內置只讀對象 type Readonly<T> = { readonly [P in keyof T]: T[P] }
      • 所以可以 arr: Readonly<string[]>arr: Readonly<Array<string>>
      • 只能訪問不可變更,不可賦值給普通數組,但接受普通數組給自己賦值(這不是個 bug 嗎)
  • 對象

    • const obj: object = { foo: 0 } obj.foo // err; 因為 object 類型下并沒有 foo 屬性
    • const arr12: { x: number } = { x: 0, y: 3 }; // err
      const arr12: { x: number } = { x: 0, y: 3 } as { x: number; y: number }; // ok
      const arr12: { x: number } = { x: 0, y: 3 } as { x: number; [key: string]: number }; //ok
      const temp = { x: 0, y: 3 };
      const arr12: { x: number } = temp; // ok
      
  • 函數

    // 指定 this 結構
    function foo(this: { name: string }, x: number): void {
      console.log(this.name, x);
    }
    foo.prototype.name = "foo";
    new foo(3);
    
    • 可以根據參數個數不同重載方法
  • Interface

    • 索引簽名 [prop: string]: Type 任意屬性名、任意屬性個數
    • 重載的方法必須全部是不可選(可選),不可一些方法可選一些方法不可選
    • 只讀屬性或方法 readonly count: number
    • 繼承多父接口時,父接口們的屬性發生沖突,繼承會報錯,需要在子接口的重寫,且重寫的屬性需要兼容所有父接口(沖突的屬性的類型)
      interface A { name: string }
      interface B { name: number }
      interface C extends A, B { name: any }
      
  • 類型別名 Type

    • type A = { name: string }; type B = A & { age: number } 效果與繼承一樣
    • 與 Interface 的區別:
      • 定義方法:type foo = (x: number) => void;interface foo { (x: number): void }
      • type 可以使用聯合類型、元組;interface 可繼承其它 interface 或 class;
      • interface 可以聲明合并 interface A { name: string }; interface A { age: string };
    • const A = class B {} “B” 只供類內部使用
    • 使用非空類型斷言“!”來通知編譯器該成員變量已經進行初始化 class { a!: number; constructor() {} },以逃避編譯器的檢查
    • 可以使用 [key: string]: number; 定義屬性,但它不符合 ts 的宗旨
    • 有 public、protected、private,主要依靠編譯器的語法檢查(ES10 中添加了 #attr 語法做為私有屬性)
    • constructor 可以重載
    • 子類重寫父類屬性,只允許放寬,protected 可轉為 public,不可轉為 private
    • 子類的構造函數必須先調用 super()再使用 this,且(ts 中)super()必須是第一個語句
    • 實例化子類順序:父類屬性 -> 父類構造函數 -> 子類屬性 -> 子類構造函
    • extends 繼承類;implements 實現接口。 類只支持單繼承;implements 可以多實現
    • 接口繼承了含有非 public 屬性的類,則此接口只能由該類的派生類實現(書 5.15.9 最后)
    • static 靜態屬性(ES10),靜態屬性可繼承(且修改其值不影響父類;不改子類改父類,子類也變;都改互不影響)
    • 抽象類 abstract:不可實例化,可繼承和被繼承,被繼承的子類可以實例化。抽象屬性不可有具體實現,且不能是 private
  • 泛型

    • function fn<T>(arg: T): T { return arg; }fn<string>('xx')

    • <定義類型 1,定義類型 2...>(arg: 使用類型): 使用類型;T 不能是 undefined 和 null

    • function fn<T1, T2 = string>(arg: T1): T2 {}fn<string, boolean>('xx')

    • 泛型約束(extends):T 可以繼承一個類型,若指定的默認類型,默認類型必須符合繼承的類型(泛型約束);實際傳入的類型也必須符合泛型約束

      function fn<T extends number = 0 | 1>(arg: T)fn<2 | 3>(3)

    • 泛型類描述的是類的實例類型,所以類的靜態成員中不允許引用類型參數

      class A<T>{ static tag: T; } // 編譯錯誤!靜態成員不允許引用類型參數 訪問 tag 時可以使用A.tag,泛型管不到靜態屬性

  • 聯合類型type T = T1 | T2 滿足一個就行;有同名不同類型的屬性,不可混用。

  • 交叉類型type T = T1 & T2 全部滿足才行;有同名不同類型的屬性,簡單類型為 never,方法則重載。

    • “&”相當于“×”,而“|”相當于“+”

      T = (string | 0) & (number | 'a');
        = (string & number) | (string & 'a') | (0 & number) | (0 & 'a');
        = never | 'a' | 0 | never;
        = 'a' | 0;
      
  • 索引類型 keyof Typekeyof any => string|number|symbolkeyof unknown => never

    • 聯合類型keyof(T1 | T2) 共有的屬性名。
    • 交叉類型type T = T1 & T2 全部滿屬性名。
  • 映射對象:type newT = { readonly [K in keyof oldT]?: oldT[K] } 復制 oldT 將所有屬性變為只讀可選的(同態映射)

    • [K in string]: number 等于 [prop: string]: number
    • 同態映射會默認拷貝源對象類型中所有屬性的 readonly 修飾符和“?”修飾符
    • 使用變量中轉過的keyof oldT不再屬于同態映射,所以不會拷貝原屬性的修飾符:type EK = keyof oldT; type newT = { [K in EK]?: oldT[K] }
    • type Required = { +readonly [K in keyof T]-?: T[K] } 刪除 ? 修飾符添加 readonly 修飾符;
      type T = {
        a?: string | undefined | null;
        readonly b: number | undefined | null;
      }
      // {
      //     readonly a: string | null;
      //     readonly b: number | undefined | null;
      // }
      type RequiredT = Required<T>; // - 號可以過刪除 undefined,but not null
      
    • 更多類型的映射方式參見 6.6.4 同態映射對象類型
  • 條件類型

    • type T = true extends boolean ? string: number; => string
    • 裸類型參數(Naked)即沒有任何裝飾([]、{}等)的類型參數;
    • 如果extends NakedType(分布式條件類型)則條件展開;
    • 內置工具類型:Exclude<T, U>Extract<T, U>Non-Nullable<T> 從聯合類型中過濾、挑選指定類型、創建非空類型;
    • T extends Array<infer U> ? U : never 推斷;inferType<number[]> => number
  • 內置工具類型(6.8 那么多,怎么可能記得住)

    • Partial<T> => { [K in keyof T]?: T[K] } 復制一份并全為可選
    • Required<T> => -? 全為必選
    • Readonly<T> => 全為只讀
    • Record<AttrNames, AttrType> => { name1: AttrType, name2: AttrType... }
    • Pick<T, AttrNames> => 從 T 中選 AttrNames 這些屬性創建新類型
    • Omit<T, AttrNames> => Pick 的互補,AttrNames 為要忽略的屬性
    • Exclude<Ks, AttrNames> => 與 Omit 的區別為 Ks = keyof T
    • Extract<Ks, AttrNames> => 與 Pick 的區別為 Ks = keyof T
    • NonNullable<T> => 從類型 T 中剔除 null 類型和 undefined 類型并構造一個新類型
    • Parameters<fnT> => 使用方法類型 fnT 的參數類型構造一個元組類型Parameters<(s: string) => void> // [string]
    • ConstructorParameters<fnT> => 構造函數 ConstructorParameters<new (s: string) => void> // [string]
    • ReturnType<fnT> => 方法類型的返回值類型 ReturnType<(s: string) => void> // void]
    • InstanceType<T> => 獲取構造函數的返回值類型,即實例類型
    • ThisParameterType<fnT> => 方法中 this 參數的類型(需要啟用“--strictFunctionTypes”編譯選項)
    • OmitThisParameter<T> => 剔除 this 參數
    • ThisType<T> => 不創建新類型 用于定義對象字面量的方法中 this 的類型???(需要啟用“--noImplicitThis”)
  • 類型查詢,對 typeof 進行了擴展 const arg2: typeof arg1 = arg1

  • 類型斷言<T>expr

    • <HTMLElement>ele = ele as HTMLElement
    • 強制類型轉換 ele as unknown as T,復雜類型之間的類型斷言,編譯器可能會無法識別出正確的類型,因此錯誤地拒絕了類型斷言操作
    • value as const,只讀字面量類型,如果 value 是個多級對象,只讀作用于所有深度
    • 非空類型斷言“!”
  • 類型細化

    • 類型守衛:typeof、instanceof、in
    • 斷言函數:function fn(x): asserts x is T or asserts x
      • 前者,只有 x 是 T 類型時,該函數才會正常返回,且此邏輯要由開發者自己實現;
      • 后者,只有 x 為真時,才會正常返回,邏輯由開發者實現;
      • 斷言函數沒有返回值。用于控制流程;

類型深入(第 7 章)

  • 字面量類型:原始類型的子類型,比如具體的值

  • never <: undefined <: null <: 其它類型

  • 函數類型重載:S 是 T 的子類型,且 T 存在函數重載,那么 T 的每一個函數重載必須能夠在 S 的函數重載中找到與其對應的子類型

  • 結構化子類型:根據對象的屬性名和類型,直接判斷兩對象間的父子關系

  • 實例的父子關系只檢查非靜態、非構造的成員屬性,包含另一個的所有成員屬性就是它是子類型。若有 protected 屬性要求兩者間有繼承關系

  • 泛型函數:noStrictGenericChecks 時只檢查參數個數不檢查類型,否則,兩者間互相帶入對方的類型進行推斷

  • 子類型在理論上可以賦值給父類型,any、數值型枚舉除外。

  • ts 命名空間基于自執行函數實現

  • 通過“tsconfig.json”中 files 配置文件能夠定義文件間的加載順序

  • 三斜線指令 /// <reference path="a.ts" /> 定義文件間依賴(a.ts 不在 tsconfig.json 中定義也會被打包)

  • 模塊

    • CommonJS:nodejs,同步加載文件,不適用于瀏覽器
    • AMD:異步模塊定義,requirejs
    • UMD:通用模塊定義,基于上是 vue 打包后的樣子
  • export ... from xxx 其它模塊的導出作為當前模塊的導出,xxx 中的功能不能在當前文件中使用。

  • 空導出使用是的模塊的副作用

  • 類型導入導出import [type|interface] ..., export [type|interface] ... 如果一個模塊只導出類型,其副作用在打包時會被刪除

  • import(uri) => Promise<Module> 異步引入模塊(es 語法,非 ts 獨有)。Module 的值相當于 import * as Module from 'uri'

  • 編譯 tsc index.ts --module [None|CommonJS|AMD|UMD|ES6|ES2020....]

  • 外部類型聲明

    • *.d.ts
    • declare const devicePixelRatio: number;
    • declare module 'io' { export function readFile(uri: string): string; }
    • declare module 'jquery'; 放棄對 jquery 插件進行類型檢查
    • 第三方插件沒有 d.ts 聲明文件時可以在 npm 官網使用@types/插件名搜索,或者于https://www.typescriptlang.org/dt/search?search=jquery處查找
    • package.json 文件中新增了 typings|types 屬性指定包的聲明文件,未指定默認為 main 同名同位置文件
  • 模塊解析 ts --moduleResolution [Classic|Node]

    • --baseUrl: '.' 非相對模塊導入的基準路徑
    • path: { "b": ["bar/b"], "@utils": ["./src/utils"] } 非相對模塊導入的映射路徑
    • rootDirs: ['src1', 'src2'] 使用不同的目錄創建出一個虛擬目錄,相對模塊導入的基準路徑
    • 外部模塊聲明 typings.d.ts(vue 中訪問不到 d.ts 中的自定義 type,目前看來是 eslint 的問題,沒有解決)
  • 聲明合并

    • 接口合并:同名 interface 會被合并成一個。屬性出現同名不同類型會報錯;方法出現同名不同類型會生成重載(入參為字面量的優先級高,后聲明的優先級高);
    • 枚舉合并:同名 enum 會被合并成一個。有相同值的枚舉合并報錯;const 與非 const 枚舉合并報錯;
    • 類合并:同名 class 不能合并。 但 declare class 可以和 interface 合并,當做對 class 類的擴展
  • 擴充模塊聲明

    import { A } from './a'
    declare module './a' {
      // 正確
      interface A {
        other: string;
      }
      // 錯誤,不能擴充頂層聲明
      interface B {
        name: string;
      }
    }
    const a: A = { other: 'A中沒有的屬性' }
    

    如果是import './a'無法對其擴充,interface 為必須

  • 擴充全局聲明

    declare global {
      interface Window {
        other: string;
      }
    }
    

ts 配置

  • 使用 tsc 指令時:

    • 路徑中的空格:tsc 'file name.ts' or tsc file\ name.ts

    • 多個文件:使用空格分割,或者使用通配符

    • -w:文件變更時自動重新編譯

    • 嚴格類型: --strict 為總開關:

      • noImplicitAny 禁用隱式 any 類型轉換
      • strictNullChecks 頂上
      • strictFunctionTypes 頂上
      • strictBindCallApply 對 call apply bind 中的 this 參數進行類型檢查
      • strictPropertyInitialization 類屬性初始化檢查
      • noImplicitThis 頂上
      • alwaysStrict 啟用 js use strict
  • tsconfig.json 若一個目錄中存在此文件,該目錄將被編譯器視作 TypeScript 工程的根目錄(或使用-p/--project tsconfig.json 來指定編譯配置)。

    • tsc --init --locale zh-CN 當前目錄初始化一個 tsconfig.json

    • compilerOptions > listFiles 打印出參與本次編譯的文件列

    • files 不支持模糊匹配,頂上

    • include 定義編譯文件列表,常與exclude一起用,以剔除 include 中不需要的文件。它們都支持模糊匹配

    • compilerOptions > typeRoots 默認指向 node_modules/@types;值為相對于 tsconfig.json 的路徑列表

    • compilerOptions > types 功能和 typeRoots 相同,但值為具體文件而非目錄

    • compilerOptions > isolatedModules 當編譯器發現無法被正確處理的語言結構時給出提示

    • tsc --showConfig 不編譯代碼,只顯示當前配置

    • extends 可以繼承其它 tsconfig.json

  • 工程引用:使用 references 引用其它工程,被引用的工程需要開啟 composite

    {
      "references": [
        { "path": "../dll1" },
        { "path": "../dll2/tsconfig.release.json" }
      ]
    }
    
    {
      "compilerOptions": {
        "composite": true,
        "declaration": true, // 必須
        "declarationMap": true // 必須,對d.ts生成sourceMap,轉到定義時跳入真實代碼位置而不是d.ts
        // 如果設置了 include 或 files,所有源文件必須都包含在內
      }
    }
    
    • tsc [--build|-b] --watch 查找工程引用,構建引用(如果有更新)

      • --verbose:打印構建日志;
      • --dry:只要日志但不真構建;
      • --clean:清理輸出文件;
      • --force:強制重新編譯;
      • --noEmit:僅類型檢查,不編譯;
    • solution 工程:在所以工程的父目前創建 tsconfig.json,references 引用所有工程,include、files 設為空數組。

  • Javascript 類型檢查

    • tsc index.js --allowJs --outDir dist 編譯 js 文件

    • --allowJs --checkJs 對 js 文件進行類型檢查

    • // @ts-nocheck 對當前 js\ts 禁用類型檢查

    • // @ts-check 對當前 js\ts 強制開啟類型檢查,無論有無--checkJs

    • // @ts-ignore 忽略下一行語句的類型檢查

    • JSDoc: 基于/** xxx */注釋

      • @typedef {(number | string )} CompanyID 生成自定義類型 CompanyID
      • @type {CompanyID} 定義變量類型
      • @param {CompanyID} cpyId - 公司id 定義參數類型
      • @param {CompanyID} [cpyId=0] - 公司id 默認參數
      • @return {CompanyID}
      • @extends {FatherClass} 定義繼承的基類
      • @public@protected@private@readonly 等等

TS 人家的例子

  • 轉譯器 tsc index.js --allowJs --target ES5 --outFile index.es5.js 將 js 文件編譯為 es5 語法

  • babel7 提供了內置的 ts 支持

    • @babel/core 核心轉譯功能
    • @babel/cli 命令行工具
    • @babel/preset-env 按需編譯和按需打補丁
    • @babel/preset-typescript ts 支持包
npm init --yes
cnpm i typescript -D
cnpm i @babel/core @babel/cli @babel/preset-env @babel/preset-typescript -D
npx babel [開發目錄] --out-dir lib --extensions \".ts,.tsx\" --source-maps inline
// .babelrc.json
{
  "presets": ["@babel/env", "@babel/typescript"]
}

名詞

  • 頂端類型:通用類型,超類型,ts 中指 any、unknown

  • 尾端類型:never

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,362評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,577評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,486評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,852評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,600評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,944評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,944評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,108評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,652評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,385評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,616評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,111評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,798評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,205評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,537評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,334評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,570評論 2 379

推薦閱讀更多精彩內容