殺手級的TypeScript功能:const斷言

翻譯:瘋狂的技術宅html

blog.logrocket.com/const-asser…前端

img

我發現官方的 TypeScript 文檔很是有用,可是總以爲有點過於學術化而且枯燥無味。每當我發現一個新功能時,我想要知道這個功能究竟可以解決什麼問題而不是長篇大論。git

在我看來,const assertions 是 TypeScript 3.4 的殺手級新功能,正如我稍後將要解釋的,咱們能夠用這個新功能省略不少繁瑣的類型聲明。typescript

const 斷言

const x = { text: "hello" } as const;
複製代碼

官方文檔中給出了這樣的解釋:redux

TypeScript 3.4 引入了一個名爲 const 斷言的字面值的新構造。它的語法是一個類型斷言,用 const 代替類型名稱(例如 123 as const)斷言構造新的文字表達式時,咱們能夠向語言發出如下信號:前端工程化

該表達式中的字面類型不該被擴展(例如:不能從「hello」轉換爲字符串)數組

對象字面量獲取只讀屬性安全

數組文字成爲只讀元組bash

感受有點枯燥,還有點混亂。讓咱們來各個擊破。markdown

沒有類型擴展的字面類型

並非每一個人都知道類型擴展,而且因爲某些意外行爲而首次發現它時都會以爲意外。

當咱們使用關鍵字 const 聲明一個字面量時,類型是等號右邊的文字,例如:

const x = 'x'; // x has the type 'x'
複製代碼

const 關鍵字確保不會發生對變量進行從新分配,而且只保證該字面量的嚴格類型。

可是若是咱們用 let 而不是 const, 那麼該變量會被從新分配,而且類型會被擴展爲字符串類型,以下所示:

let x = 'x'; // x has the type string;
複製代碼

如下是兩個不一樣的聲明:

const x = 'x'; // has the type 'x' 
let y = 'x';   // has the type string
複製代碼

y 被擴展爲更通用的類型,並容許將其從新分配給該類型的其餘值,而變量 x 只能具備 'x'的值。

用新的 const 功能,我能夠這樣作:

let y = 'x' as const; // y has type 'x'`
複製代碼

對象字面量獲取只讀屬性

在 Typescript 3.4 以前,類型擴展發生在對象字面量中:

const action = { type: 'INCREMENT', } // has type { type: string }
複製代碼

即便咱們將 action 聲明爲 const,仍然能夠從新分配 type 屬性,所以,該屬性被擴展成了字符串類型。

這看上去使人以爲不是那麼有用,因此讓咱們換一個更好的例子。

若是你熟悉 Redux,就可能會發現上面的 action 變量能夠用做 Redux action。若是你不知道 Redux 我來簡單解釋一下,Redux 是一個全局不可變的 state 存儲。經過向所謂的 reducers 發送動做來修改狀態。 reducers 是純函數,它在調度每一個 action 後返回全局狀態的新更新版本,以反映 acion 中指定的修改。

在 Redux 中,標準作法是從名爲 action creators 的函數建立操做。 action creators 只是純函數,它返回 Redux操做對象字面量以及提供給函數的全部參數。

用一個例子能夠更好地說明這一點。應用程序可能須要一個全局 count 屬性,爲了更新這個 count 屬性,咱們能夠調度類型爲 'SET_COUNT' 的動做,它只是將全局 count 屬性設置爲一個新的值,這是一個字面對象屬性。這個 action 的 action creator 將是一個函數,它接受一個數字做爲參數,並返回一個具備屬性爲 type、值爲 SET_COUNT 和類型爲 number 的 payload 屬性的對象,它將指定 count 的新值:

const setCount = (n: number) => {
  return {
    type: 'SET_COUNT',
    payload: n,
  }
}

const action = setCount(3)
// action has type
// { type: string, payload: number }
複製代碼

從上面顯示的代碼中能夠看出,type 屬性已經被擴展爲 string 類型而再也不是 SET_COUNT。這不是很安全的類型,咱們能夠保證的是 type 是一個字符串。 redux 中的每一個 action 都有一個 type 屬性,它是一個字符串。

這不是很好,若是咱們想要利用 type 屬性上的可區分聯合的話,那麼在 TypeScript 3.4 以前,則須要爲每一個 action 聲明一個接口或類型:

interface SetCount {
  type: 'SET_COUNT';
  payload: number;
}

const setCount = (n: number): SetCount => {
  return {
    type: 'SET_COUNT',
    payload: n,
  }
}

const action = setCount(3)
// action has type SetCount
複製代碼

這確實增長了編寫 Redux action 和 reducers 的負擔,但咱們能夠經過添加一個 const assertion 來解決這個問題:

const setCount = (n: number) => {
  return <const>{
    type: 'SET_COUNT',
    payload: n
  }
}

const action = setCount(3);
// action has type
// { readonly type: "SET_COUNT"; readonly payload: number };
複製代碼

你會注意到從 setCount 推斷的類型已經在每一個屬性中附加了 readonly 修飾符,正如文檔的項目符號所述。

這就是所發生的事情:

{
  readonly type: "SET_COUNT";
  readonly payload: number
};
複製代碼

action 中的每一個字面量都被添加了 readonly 修飾符。

在 redux 中,咱們建立了一個接受 action 的聯合,reducer 函數能夠經過這種操做來得到良好的類型安全性。在 TypeScript 3.4 以前,咱們會這樣作:

interface SetCount {
  type: 'SET_COUNT';
  payload: number;
}

interface ResetCount {
  type: 'RESET_COUNT';
}

const setCount = (n: number): SetCount => {
  return {
    type: 'SET_COUNT',
    payload: n,
  }
}

const resetCount = (): ResetCount => {
  return {
    type: 'RESET_COUNT',
  }
}

type CountActions = SetCount | ResetCount
複製代碼

咱們建立了兩個接口 RESET_COUNTSET_COUNT 來對兩個 resetCountsetCount 的返回類型進行歸類。

CountActions 是這兩個接口的聯合。

使用 const assertions,咱們能夠經過使用 constReturnTypetypeof 的組合來消除聲明這些接口的須要:

const setCount = (n: number) => {
  return <const>{
    type: 'SET_COUNT',
    payload: n
  }
}

const resetCount = () => {
  return <const>{
    type: 'RESET_COUNT'
  }
}

type CountActions = ReturnType<typeof setCount> | ReturnType<typeof resetCount>;
複製代碼

咱們從 action creator 函數 setCountresetCount 的返回類型中推斷出一個很好的 action 聯合。

數組字面量成爲只讀元組

在 TypeScript 3.4 以前,聲明一個字面量數組將被擴展而且能夠修改。

使用 const,咱們能夠將字面量鎖定爲其顯式值,也不容許修改。

若是咱們有一個用於設置小時數組的 redux action 類型,它可能看起來像這樣:

const action = {
  type: 'SET_HOURS',
  payload: [8, 12, 5, 8],
}
// { type: string; payload: number[]; }

action.payload.push(12) // no error
複製代碼

在 TypeScript 3.4 以前,擴展會使上述操做的字面量屬性更加通用,由於它們是能夠修改的。

若是咱們將 const 應用於對象字面量,那麼就能夠很好地控制全部內容:

const action = <const>{
  type: 'SET_HOURS',
  payload: [8, 12, 5, 8]
}

// {
// readonly type: "SET_HOURS";
// readonly payload: readonly [8, 12, 5, 8];
// }

action.payload.push(12);  // error - Property 'push' does not exist on type 'readonly [8, 12, 5, 8]'.
複製代碼

這裏發生的事情偏偏是文檔的要點:

payload 數組確實是 [8,12,5,8] 的「只讀」元組(不過我並無從文檔中看到這方面的說明)。

結論

我用如下代碼總結以上全部內容:

let obj = {
  x: 10,
  y: [20, 30],
  z: {
    a:
      {  b: 42 }
  } 
} as const;
複製代碼

對應於:

let obj: {
  readonly x: 10;
  readonly y: readonly [20, 30];
  readonly z: {
    readonly a: {
      readonly b: 42;
    };
  };
};
複製代碼

在這裏,我能夠推斷出類型,而不是去編寫多餘的樣板類型。這對於 redux 特別有用。

歡迎關注前端公衆號:前端先鋒,獲取「前端工程化實用工具包」

相關文章
相關標籤/搜索