TypeScript中那幾個奇怪的知識點

寫在開頭

  • 我寫了一年多TypeScript,總結了如下幾個點,但願能夠幫到你們
  • 若是感受寫得不錯,記得來個關注/在看

比較容易遇到的問題

給一個對象添加屬性
interface Obj {
  a: string;
}

const obj: Obj = {
  a: "1",
};

obj.b = 2;
  • 此時會出現錯誤提示:類型「Obj」上不存在屬性「b」。
  • 要想解決這個問題,要使用索引簽名
interface Obj {
  a: string;
  [index: string]: string | number;
}

const obj: Obj = {
  a: "1",
};

obj.b = 2;
  • 你們很好奇,爲何我這裏會加入[index: string]: string | number;,類型是字符串或者數字。由於:
當你聲明一個索引簽名時,全部明確的成員都必須符合索引簽名
函數重載
  • 場景:函數有多個參數,並且參數不肯定時,函數運行邏輯不一致
// 重載
function padding(all: number);
function padding(topAndBottom: number, leftAndRight: number);
function padding(top: number, right: number, bottom: number, left: number);
// Actual implementation that is a true representation of all the cases the function body needs to handle
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a;
  } else if (c === undefined && d === undefined) {
    c = a;
    d = b;
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  };
}
這樣函數兼容 傳 一、二、4個參數。 可是隻要傳三個,就會報錯。
  • 函數重載最重要的是,最終聲明(從函數內部看到的真正聲明)與全部重載兼容(與上面的索引簽名一致)
下載的第三方npm庫沒有ts聲明文件
  • 例如:
npm i somePackage --save 
import somePackage from 'somePackage';
  • 可是此時提示:找不到模塊「somePackage」或其相應的類型聲明。
  • 此時你能夠在項目根目錄下新建index.d.ts,編寫以下代碼:
declare module 'somePackage';
...
這個問題迎刃而解
泛型
  • 這個問題很容易困擾小白,其實泛型簡單來講,就是一個類型變量,以下所示:
class Peter {
  niubi<T>(a: T): T[] {
    return [a];
  }
}
此時的T就是一個泛型,它是一個可變的類型。根據你傳入 niubi這個方法的參數對象來肯定的,當咱們傳入的 a是字符串,那麼T就爲 string.返回的就是一個 item爲字符串的數組
class Peter {
  niubi<T>(a: T): T[] {
    return [a];
  }
}

const obj = new Peter();

let res = obj.niubi("hehe");

res = 1;

res = ["2"];

此時res = 1會報錯不能將類型「number」分配給類型「string[]」, 由於此時TS推斷出來,res一定爲一個數組,且裏面的item是一個字符串.ios

res = ["2"]則不會報錯
  • 泛型能夠說是TS裏面的一個難點,可是其實它只是一個可變的類型變量。
  • 調整參數後:
let res2 = obj.niubi(2);

res2 = 2;
  • 會報錯:不能將類型「number」分配給類型「number[]」。
最後要記住的是,既然是類型變量。那麼這個變量也能夠是一個泛型。
class Peter {
  niubi<T>(a: T): T[] {
    return [a];
  }
}

const obj = new Peter();

function test<T>(b: T): T {
  return b;
}

let res = obj.niubi(test(1));
  • 看到這裏確定有人會說,Peter你脫褲子放屁啊。這個還不如用any.那你再看下面這段代碼,咱們封裝api請求的時候。
  • 首先定義好返回的接口。(返回的接口通常都是有統一的格式,狀態碼和result,data等)
// 請求接口數據
export interface ResponseData<T = any> {
  /**
   * 狀態碼
   * @type { number }
   */
  code: number;

  /**
   * 數據
   * @type { T }
   */
  result: T;

  /**
   * 消息
   * @type { string }
   */
  message: string;
}
  • 這裏的data數據是動態的格式,咱們能夠用泛型來定義。
  • 這裏用了兩次泛型,先定義好返回的data數據,再用泛型方式傳入,組裝好返回的整個返回數據接口(包含code,result,data)。再接着傳入到真正的請求函數中
// 在 axios.ts 文件中對 axios 進行了處理,例如添加通用配置、攔截器等
import Ax from './axios';

import { ResponseData } from './interface.ts';

export function getUser<T>() {
  return Ax.get<ResponseData<T>>('/somepath')
    .then(res => res.data)
    .catch(err => console.error(err));
}
  • 在真正的請求函數中使用了泛型,即傳入任意類型參數<T>,那麼便返回一個Promise風格的Promise<T>數據 :
const get = <T>(config: { url: string; headers?: { [key: string]: string } }): Promise<T> => {
  const fetchConfig = {
    method: 'GET',
    Accept: 'application/json',
    'Content-Type': 'application/json',
    ...(config.headers || {})
  };
  return fetch(config.url, fetchConfig).then<T>(response => response.json());
};
總結兩次泛型的連續使用:

1.使用data做爲泛型,傳入npm

2.組裝成{code,result,data}這種類型接口json

3.將第二步的組裝後類型做爲泛型<T>傳入get方法中axios

4.返回一個Promise風格的Promise<T>數據api

這樣作的意義,提取可變的數據類型 data,讓TS推斷出這個接口返回的數據是怎麼樣的。減小沒必要要的重複代碼,即每次接口調取都會返回的數據格式類型: coderesult
  • 相信你經過這段代碼和文字,能真正理解TS的泛型如何用,什麼地方使用,以及使用的意義了。
顆粒度定義類型後的問題
  • 當咱們顆粒度比較細定義了接口之後,可能存在接口複用的問題,例如:
interface test1 {
  a: string;
}

interface test2 {
  b: string;
}
  • 此時我想要定義一個兩個屬性都擁有的對象,那麼能夠使用聯合類型。
const obj: test1 & test2 = {
  a: "1",
  b: "2",
};
  • 若是我想定義一個只有a/b的對象,能夠使用
const obj: test1 | test2 = {
  a: "1",
};
可能有人會說,怎麼會寫這麼簡單的東西。
  • 這裏是爲了接下來的類型兼容性打基礎,TS裏面最重要的就是type類型,類型系統就是它的核心。
  • 咱們能夠用兩個不一樣的變量來互相賦值來檢驗,他們的類型是否兼容,例如:
interface Test1 {
  a: number;
  b: number;
  c: string;
}

interface Test2 {
  a: number;
  b: number;
}

let test1: Test1 = {
  a: 1,
  b: 2,
  c: "3",
};

let test2: Test2 = {
  a: 1,
  b: 2,
};

test1 = test2;
此時提示 類型 "Test2" 中缺乏屬性 "c",但類型 "Test1" 中須要該屬性。
  • 可是當咱們用test1賦值給test2的時候:
test2 = test1;
這個時候是能夠的
  • 這裏其實隱藏着一些邏輯,Test1接口比Test2接口多一個c屬性,Test2接口能夠說是Test1接口的子類。這是多態性
關於如何處理、判斷TS的類型兼容性,你們能夠看下面這些類型
  • 協變(Covariant):只在同一個方向;
  • 逆變(Contravariant):只在相反的方向;
  • 雙向協變(Bivariant):包括同一個方向和不一樣方向;
  • 不變(Invariant):若是類型不徹底相同,則它們是不兼容的。

寫在最後

相關文章
相關標籤/搜索