翻譯:瘋狂的技術宅html
我發現官方的 TypeScript 文檔很是有用,可是總以爲有點過於學術化而且枯燥無味。每當我發現一個新功能時,我想要知道這個功能究竟可以解決什麼問題而不是長篇大論。git
在我看來,const assertions
是 TypeScript 3.4 的殺手級新功能,正如我稍後將要解釋的,咱們能夠用這個新功能省略不少繁瑣的類型聲明。typescript
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_COUNT
和 SET_COUNT
來對兩個 resetCount
和 setCount
的返回類型進行歸類。
CountActions
是這兩個接口的聯合。
使用 const assertions
,咱們能夠經過使用 const
、 ReturnType
和 typeof
的組合來消除聲明這些接口的須要:
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 函數 setCount
和 resetCount
的返回類型中推斷出一個很好的 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 特別有用。