TS使用歸檔

TS特有de概念

元組

指定數組每個元素的類型

特點

  • 在初始化時,必須按限定的類型和個數賦值;
  • 可以通過數組的方法突破限制;

示例

let arr: [string, number, any]
arr = ['1', 1, 1] // OK
arr = [1, 1, 1] // Error
arr = ['1', 1, 1, true] // Error
arr.push(true) // OK

接口

只是在ts編譯階段做類型檢查,并不會轉譯成JS代碼

用接口聲明可調用的類型

示例

// 定義函數
interface ISum {
    (x: number, y: number, z?: number): number;
}
let sum: ISum
sum = (a) => {
//     ^ = OK
  return 1
}
sum('1', 2)
//   ^ Argument of type '"1"' is not assignable to parameter of type 'number'.


// 定義類的new調用
interface IConstructor {
  new(arg: string): IConstructor;
}
function Constr (this: any, name: string) {
  this.name = name
}
const instance = new (Constr as any as IConstructor)('111')

以上示例注意:

  • interface定義函數輸入、輸出的類型作為預置值內置在函數聲明中
    • 函數聲明時無需二次定義參數類型,函數輸出值的類型推斷要與interface定義的輸出類型一致,否則會報錯。
    • interface定義函數沒有限制函數聲明時傳入的參數個數,只有在調用時才會報參數個數錯誤;
  • 函數聲明無法直接其它類型,需要使用雙重斷言Constr as any as IConstructor
    • ==盡可能不要使用雙重斷言==,它會影響ts的判斷
    // 示例:
    let num: number = 0
    let str: string = 's'
    num = str // Error
    num = str as any as number // OK
    //^ = num === 's' //這里str認為是number類型,賦值成功
    

聯合類型

一個數據聲明為聯合類型,使用時,若不確定是聯合類型中具體的類型時(通過if條件、as斷言、in操作符、typeof縮小未知范圍),只能訪問聯合類型共有的方法。

斷言

斷言是聯合類型縮小未知范圍時使用,但,斷言強制瀏覽器相信數據是我們指定的類型,實際上是不安全的。【推薦】使用類型收縮typeofinstanceof...來縮小未知范圍。

當兩個類型聲明有交集時,才可以使用斷言,否則會報錯(because neither type sufficiently overlaps with the other.)

如果兩個類型聲明沒有交集,可以使用雙重斷言強制斷言成另一種類型,示例如上:Constr as any as IConstructor

readonly & Readonly泛型

readonly標識符,用于對象屬性

Readonly映射類型,接收一個泛型T,用來把泛型T的所有屬性標記為只讀類型;

示例

type Foo = {
  bar: number;
  bas: number;
}
type ReadFoo = Readonly<Foo>
/**     ^ = type ReadFoo = {
*               readonly bar: number;
*               readonly bas: number;
*           }
*/

示例

function fn(x: number | string): number {
  return x.length;
}
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.

對象結構

示例

// type定義對象結構,不可重載
type TObjectProps = {
  x: number;
  y: number;
}
const obj: TObjectProps = {
  x: 1,
  y: 1
}




// interface定義對象結構
interface IObjectProps {
  x: number;
  y: number;
}
const obj: IObjectProps = {
  x: 1,
  y: 1,
  add() {}
  //^ = 'add' does not exist in type 'IObjectProps'
}

// interface定義重載
interface IObjectProps {
  x: number;
  y: number;
}
const obj: IObjectProps = {
  x: 1,
  y: 1,
  add() {} // OK
}
interface IObjectProps {
  add: () => void;
}




// let & typeof定義對象結構,不可重載
let objectProps: {
  x: number;
  y: number;
}
const obj: typeof objectProps = {
  x: 1,
  y: 1
}

Function

函數類型聲明方式有多種,應用場景兩種: 固定參數,不固定參數;

示例

固定參數:
// type定義
type Tfn = (a: number, b: number) => number;
let fn1: Tfn
fn1 = function(a, b) {
  return  a + b
}
fn1(1, 2)


// type定義重載
type Tfn = {
  (a: string): string;
  (a: number, b: number): number;
}
let fn1: Tfn
fn1 = function(a: any, b?: any): any {
  if (b) {
    return a + b
  } else {
    return a
  }
}
fn1('1')
//  ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
//  ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1)  //Error



// interface定義
interface Ifn {
  (x: string): string;
}
let fn1: Ifn
fn1 = function(a) {
  return a
}
fn1('1')

// interface定義重載
interface Ifn {
  (x: string): string;
  (x: number, y: number): number;
}
let fn1: Ifn
fn1 = function(a: any, b?: any): any {
  if (b) {
    return a + b
  } else {
    return a
  }
}
fn1('1')
//  ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
//  ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1)  //Error



// let & typeof 聲明
let fn: {
  (x: string): string;
}
let fn1: typeof fn
fn1 = function(a) {
  return a
}
fn1('1')

// let & typeof聲明重載
let fn: {
  (x: string): string;
  (x: number, y: number): number;
}
let fn1: typeof fn
fn1 = function(a: any, b?: any) {
  if (b) {
    return a + b
  } else {
    return a
  }
}
fn1('1')
//  ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
//  ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1)  //Error



// function聲明
function fn(x: string): string {
  return x
}
fn('1')

// function聲明重載:fn實現必須緊跟著fn頭聲明
function fn(x: string): string;
function fn(x: number, y: number): number;
function fn(x: any, y?: any) {
  if (y) {
    return x + y
  } else {
    return x
  }
}
fn('1')
//  ^ = let fn: (a: string) => string (+1 overload)
fn(1, 2)
//  ^ = let fn: (a: number, b: number) => number (+1 overload)
fn(1)  //Error

通過重復定義函數頭實現重載,而函數聲明輸入、輸出最好使用any類型(不使用any的話,函數體的操作邏輯使用的必須是聯合聲明類型共有的成員),調用時會根據參數個數匹配預定義的函數頭進行校驗。

不固定參數:
// 應用場景:作為回調函數,通過`apply`調用;
interface IFn {
  (...args: any[]): any;
}
function invoke(fn: IFn) {
  fn.apply(null, [...arguments])
}

枚舉Enum

定義索引和值的雙向映射;

enum Direction {
  UP,
  DOWN,
  LEFT,
  RIGHT
}
console.log(Direction[0]) // 'UP'
console.log(typeof Direction[0]) // 'string'
console.log(Direction['UP']) // 0
console.log(typeof Direction['UP']) // 'number'
console.log(Direction[0] === 'UP') // true

分類

數字枚舉

數字枚舉默認從0開始;

若有指定的索引,則后續數據索引++

// 場景:Code編碼語義化
enum Direction {
  Up,
  Down = 10,
  Left,
  Right
}
console.log(Direction[0]) // 'UP'
console.log(Direction['Up']) // 0
console.log(Direction['Left']) // 11
console.log(Direction[10]) // 'Down'
字符串枚舉
// 場景:游戲按鍵?
enum Direction {
  Up = 'u',
  Down = 'd',
  Left = 'l',
  Right = 'r'
}
console.log(Direction['Up']) // '上'
console.log(Direction['Down']) // '下'
常量枚舉

和上述枚舉類型編譯結果不同

enum Dir {
  Up,
  Down = 10,
  Left,
  Right
}
const enum Direction {
  Up,
  Down = 10,
  Left,
  Right
}
let directions = [
  Direction.Up,
  Direction.Down,
  Direction.Left,
  Direction.Right,
];

/////編譯輸出如下:
"use strict";
var Dir;
(function (Dir) {
    Dir[Dir["Up"] = 0] = "Up";
    Dir[Dir["Down"] = 10] = "Down";
    Dir[Dir["Left"] = 11] = "Left";
    Dir[Dir["Right"] = 12] = "Right";
})(Dir || (Dir = {}));
let directions = [
    0 /* Up */,
    10 /* Down */,
    11 /* Left */,
    12 /* Right */,
];

字面量類型

const str: 'name' = 'name' // str只能是'name'字符串,賦值其他字符串或其他類型會報錯;
const number: 1 = 1 // number只能是1,賦值其他數值或其他類型會報錯;
type Directions = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
let toWhere: Directions = 'LEFT' 

對應場景:

interface IO {
  'y+': number;
  'M+': number;
  'd+': number;
  'h+': number;
  'm+': number;
  's+': number;
  'q+': number;
  'S+': number;
}
type TKeyProps = keyof IO
//  ^ = type TKeyProps = "y+" | "M+" | "d+" | "h+" | "m+" | "s+" | "q+" | "S+"
var o: IO = {
  'y+': this.getFullYear(),
  'M+': this.getMonth() + 1, 
  'd+': this.getDate(),
  'h+': this.getHours(),
  'm+': this.getMinutes(),
  's+': this.getSeconds(),
  'q+': Math.floor((this.getMonth() + 3) / 3),
  'S+': this.getMilliseconds()
}
o['y++'] // OK
let kkk = 's+'
o[kkk] // Error
o[kkk as TKeyProps] // OK

泛型

泛型de目的是在成員之間(至少有兩個地方用到了泛型占位)提供有意義的約束

成員:

  • 類的屬性
  • 類的方法
  • 函數參數
  • 函數返回值

泛型在定義時,不能明確數據類型,聲明一變量占位,調用時通過傳入類型或ts類型推斷來確定具體的數據類型。

  • 相對于any:泛型未丟失數據結構信息;
  • 相對于聯合聲明:泛型明確具體的類型結構,聯合聲明并未明確具體類型;

邏輯中只能調用泛型數據的通用成員屬性/方法,否則會報錯;

示例

function identity<T>(arg: T): T {
    return arg;
}
// 明確指定T是string類型
let output = identity<string>("myString");  // type of output will be 'string'
// 利用類型推論 -- 即編譯器會根據傳入的參數自動地幫助確定T的類型
let output = identity("myString");  // type of output will be 'string'

any & never & unknown

  • any
    • 稱為top type,任何類型的值都能賦給any類型的變量
    • 又稱為bottom type,任何類型(除never外)的子類型
    • 可以理解為沒有類型
  • never
    • 稱為bottom type,任何類型的子類型,也可以賦值給任何類型

    • 推斷場景1:

      無法執行到函數終止點的函數表達式

      • 場景:總拋出異常的函數表達式的返回值類型;
        // 需要是函數表達式,若是函數聲明為assertNever: () => void
        const assertNever = function (x: any) {
        //      ^ = type assertNever = never
          throw new Error("Unexpected object: " + x);
        }
      
      • 場景:永不結束函數表達式的返回值類型;
        let loop = function () {
        //    ^ = type loop = never
          while (true) {}
        }
      
    • 推斷場景2:被永不為真的類型保護約束的變量類型;

      //  示例:
        type result = 1 & 2 // 結果為never
        //      ^ = type result = never
      // 尤大示例:
        interface Foo {
          type: 'foo'
        }
      
        interface Bar {
          type: 'bar'
        }
      
        type All = Foo | Bar
        function handleValue(val: All) {
          switch (val.type) {
            case 'foo':
              // 這里 val 被收窄為 Foo
              break
            case 'bar':
              // val 在這里是 Bar
              break
            default:
              const exhaustiveCheck = val
              //      ^ = type exhaustiveCheck = never
              break
          }
        }
    
  • unknown
    • top type:任何類型都是它的subtype
    • 不能將unknown賦值其它類型,unknown類型的數據只能賦值給unknownany類型;
    • 相對于anyts會為unknown提供有效的類型檢測;
    • unknown類型數據的屬性或方法,需要通過類型斷言/類型收縮來縮小未知范圍;

any的危害

使用any做類型聲明或者做斷言,會喪失原始數據的結構類型信息,即:再也無法知道原始數據是什么結構了,更不會有報錯信息,推薦使用unknown & 類型收縮

索引簽名

索引簽名用于定義對象/數組de通配結構

索引簽名的名稱(如:{ [key: string]: number }key )除了可讀性外,沒有任何意義,可以任意定義。

其它成員都必須符合索引簽名值de結構,所以,索引簽名值de結構必須是其它成員屬性類型的top type

盡量不要使用字符串索引簽名與有效變量混合使用——如果屬性名稱中有拼寫錯誤,這個錯誤不會被捕獲。【同級的其它屬性應該是對索引簽名的限制增強

示例

// 1. 對象示例
interface Foo {
  [key: string]: number; // 該通配結構[key: string]即是簽名索引。
}
// 1. 數組示例
interface Foo {
  [idx: number]: string;
  length: number;
}
const arr: Foo = ['1', '2', '3', '4']



// 2. 所有明確的成員都必須符合索引簽名
  interface Bar {
    [key: string]: number;
    x: number; // OK
    y: string; 
  //^ = Property 'y' of type 'string' is not assignable to string index type 'number'.
  }
// 2. 可以使用交叉類型突破限制
  interface Bar {
    [key: string]: number;
    x: number; 
  }
  type Composition = Bar && {
    y: string;  
  }



// 3. 有效變量和索引簽名不要同級定義
interface CSS {
  [selector: string]: string;
  color?: string; 
}
const failsSilently: CSS = {
  colour: 'red' // 'colour' 不會被捕捉到錯誤
};

TS類型聲明

ts報錯對應查找:做詞典用;

declare聲明通過各種途徑(<script>導入、new webpack.ProvidePlugin({//...}))引入全局命名空間的變量/模塊

函數聲明中顯式使用this

// 場景示例:
function Person() {
  var vm = this;
  //         ^ = this具有隱式類型any;
}



// 解決方案:
function Person(this: void) {
  var vm = this;
}

this作為函數的第一參數,由于ts只是類型檢查,編譯成JS時不會將this參數輸出。

全局庫聲明

// 場景示例:
$('div')[]
//^ = not find name '$'




// 解決方案:追加zepto.d.ts聲明
interface ZeptoStatic {
  //...
}
interface ZeptoCollection {
  // ...
}
declare var Zepto: (fn: ($: ZeptoStatic) => void) => void;
declare var $: ZeptoStatic;
declare function $(selector?: string, context?: any): ZeptoCollection;

JS內置對象追加屬性聲明

// 場景示例:
Date.prototype.format = function() {
  //...
}

const arg = 2423423413;
new Date().format(arg)
//          ^ = format 不在Date上 





// 解決方案:追加聲明*.d.ts聲明
declare global {
  interface Date {
    Format(arg: string): string;
  }
}

圖片...靜態資源聲明

// 場景示例:
import Logo from './assets/logo.png'
//      ^ = not find Module




// 解決方案:追加聲明*.d.ts聲明
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'

vue3配置全局成員

// 場景示例:
// main.ts
import { createApp } from 'vue'
import axios from 'axios'
import qs from 'qs'
import App from './App.vue'

const vueInstance = createApp(App)
vueInstance.config.globalProperties.$http = axios
vueInstance.config.globalProperties.$qs = qs

// *.vue
...
this.$http.post(...).then(() => {})
//    ^ = Property '$http' does not exist on type 'ComponentPublicInstance<>'...
...




// 解決方案:追加聲明*.d.ts
import Axios from "axios";
import qs from 'qs'
import Store from "../store";

declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $http: Axios;
    $store: Store;
    $qs: qs;
  }
}

第三方ES Module:export default聲明

// 場景示例:
// utils.ts
export default (function() {
  const utils = {
      
  }
  
  return utils
})() 
// *.ts
utils.default.isString(123)
//    ^ = Property 'default' does not exist on type 'typeof utils'






// 解決方案:追加聲明*.d.ts
declare namespace utils {
  const UtilsProps: {
    isString: (arg: unknown) => boolean;
    isArray: (arg: unknown)=> boolean;
    isObject: (arg: unknown)=> boolean;
    isDate: (arg: unknown)=> boolean;
    app: any;
  }
  export default UtilsProps;
}

第三方ES Module:export聲明

// *.d.ts
declare namespace utils {
  export function isString (arg: unknown): boolean;
  export var app: any;
}

第三方CommonJS模塊聲明

// 模塊類庫  module-lib.ts
function moduleLib(options) {
   console.log(options);
}

const version = "1.0.0";
function doSomething() {
   console.log('moduleLib do something');
}
moduleLib.version = version;
moduleLib.doSomething = doSomething;
module.exports = moduleLib;





// *.d.ts
declare function moduleLib(options: Options): void;
interface Options {
   [key: string]: any,
}
declare namespace moduleLib{
   const version: string;
   function doSomething(): void;
}
export = moduleLib;

第三方'UMD'聲明

// UMD庫  umd-lib.js
(function (root, factory) {
   if (typeof define === "function" && define.amd) {
      define(factory);
   } else if(typeof module === "object" && module.exports) {
      module.exports = factory();
   } else {
      root.umdLib = factory();
   }
})(this, function () {
   return {
      version: "1.0.2",
      doSomething() {
         console.log('umdLib do something');
      }
   }
});






// *.d.ts文件
declare namespace umdLib {
   const version: string;
   function doSomething(): void;
}
export as namespace umdLib // 專門為umd庫準備的語句,不可缺少
export = umdLib // commonjs導出

參考書

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

推薦閱讀更多精彩內容