瘋狂的技術宅 前端先鋒 前端
每日前端夜話0x6F
每日前端夜話,陪你聊前端。
天天晚上18:00準時推送。
正文共:1916 字
預計閱讀時間: 6 分鐘
翻譯:瘋狂的技術宅
來源:logrockettypescript
我發現官方的 TypeScript 文檔很是有用,可是總以爲有點過於學術化而且枯燥無味。每當我發現一個新功能時,我想要知道這個功能究竟可以解決什麼問題而不是長篇大論。redux
在我看來,const assertions 是 TypeScript 3.4 的殺手級新功能,正如我稍後將要解釋的,咱們能夠用這個新功能省略不少繁瑣的類型聲明。數組
const 斷言安全
1const x = { text: "hello" } as const;
官方文檔中給出了這樣的解釋:ide
TypeScript 3.4 引入了一個名爲 const 斷言的字面值的新構造。它的語法是一個類型斷言,用 const 代替類型名稱(例如 123 as const)斷言構造新的文字表達式時,咱們能夠向語言發出如下信號:
該表達式中的字面類型不該被擴展(例如:不能從「hello」轉換爲字符串)
對象字面量獲取只讀屬性
數組文字成爲只讀元組函數
感受有點枯燥,還有點混亂。讓咱們來各個擊破。翻譯
並非每一個人都知道類型擴展,而且因爲某些意外行爲而首次發現它時都會以爲意外。code
當咱們使用關鍵字 const 聲明一個字面量時,類型是等號右邊的文字,例如:對象
1const x = 'x'; // x has the type 'x'
const 關鍵字確保不會發生對變量進行從新分配,而且只保證該字面量的嚴格類型。
可是若是咱們用 let 而不是 const, 那麼該變量會被從新分配,而且類型會被擴展爲字符串類型,以下所示:
1let x = 'x'; // x has the type string;
如下是兩個不一樣的聲明:
1const x = 'x'; // has the type 'x' 2let y = 'x'; // has the type string
y 被擴展爲更通用的類型,並容許將其從新分配給該類型的其餘值,而變量 x 只能具備 'x'的值。
用新的 const 功能,我能夠這樣作:
1let y = 'x' as const; // y has type 'x'`
在 Typescript 3.4 以前,類型擴展發生在對象字面量中:
1const 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 的新值:
1const setCount = (n: number) => { 2 return { 3 type: 'SET_COUNT', 4 payload: n, 5 } 6} 7 8const action = setCount(3) 9// action has type 10// { type: string, payload: number }
從上面顯示的代碼中能夠看出,type 屬性已經被擴展爲 string 類型而再也不是 SET_COUNT。這不是很安全的類型,咱們能夠保證的是 type 是一個字符串。 redux 中的每一個 action 都有一個 type 屬性,它是一個字符串。
這不是很好,若是咱們想要利用 type 屬性上的可區分聯合的話,那麼在 TypeScript 3.4 以前,則須要爲每一個 action 聲明一個接口或類型:
1interface SetCount { 2 type: 'SET_COUNT'; 3 payload: number; 4} 5 6const setCount = (n: number): SetCount => { 7 return { 8 type: 'SET_COUNT', 9 payload: n, 10 } 11} 12 13const action = setCount(3) 14// action has type SetCount
這確實增長了編寫 Redux action 和 reducers 的負擔,但咱們能夠經過添加一個 const assertion 來解決這個問題:
1const setCount = (n: number) => { 2 return <const>{ 3 type: 'SET_COUNT', 4 payload: n 5 } 6} 7 8const action = setCount(3); 9// action has type 10// { readonly type: "SET_COUNT"; readonly payload: number };
你會注意到從 setCount 推斷的類型已經在每一個屬性中附加了 readonly 修飾符,正如文檔的項目符號所述。
這就是所發生的事情:
1{ 2 readonly type: "SET_COUNT"; 3 readonly payload: number 4};
action 中的每一個字面量都被添加了 readonly 修飾符。
在 redux 中,咱們建立了一個接受 action 的聯合,reducer 函數能夠經過這種操做來得到良好的類型安全性。在 TypeScript 3.4 以前,咱們會這樣作:
1interface SetCount { 2 type: 'SET_COUNT'; 3 payload: number; 4} 5 6interface ResetCount { 7 type: 'RESET_COUNT'; 8} 9 10const setCount = (n: number): SetCount => { 11 return { 12 type: 'SET_COUNT', 13 payload: n, 14 } 15} 16 17const resetCount = (): ResetCount => { 18 return { 19 type: 'RESET_COUNT', 20 } 21} 22 23type CountActions = SetCount | ResetCount
咱們建立了兩個接口 RESET_COUNT 和 SET_COUNT 來對兩個 resetCount 和 setCount 的返回類型進行歸類。
CountActions 是這兩個接口的聯合。
使用 const assertions,咱們能夠經過使用 const、 ReturnType 和 typeof 的組合來消除聲明這些接口的須要:
1const setCount = (n: number) => { 2 return <const>{ 3 type: 'SET_COUNT', 4 payload: n 5 } 6} 7 8const resetCount = () => { 9 return <const>{ 10 type: 'RESET_COUNT' 11 } 12} 13 14type CountActions = ReturnType<typeof setCount> | ReturnType<typeof resetCount>;
咱們從 action creator 函數 setCount 和 resetCount 的返回類型中推斷出一個很好的 action 聯合。
在 TypeScript 3.4 以前,聲明一個字面量數組將被擴展而且能夠修改。
使用 const,咱們能夠將字面量鎖定爲其顯式值,也不容許修改。
若是咱們有一個用於設置小時數組的 redux action 類型,它可能看起來像這樣:
1const action = { 2 type: 'SET_HOURS', 3 payload: [8, 12, 5, 8], 4} 5// { type: string; payload: number[]; } 6 7action.payload.push(12) // no error
在 TypeScript 3.4 以前,擴展會使上述操做的字面量屬性更加通用,由於它們是能夠修改的。
若是咱們將 const 應用於對象字面量,那麼就能夠很好地控制全部內容:
1const action = <const>{ 2 type: 'SET_HOURS', 3 payload: [8, 12, 5, 8] 4} 5 6// { 7// readonly type: "SET_HOURS"; 8// readonly payload: readonly [8, 12, 5, 8]; 9// } 10 11action.payload.push(12); // error - Property 'push' does not exist on type 'readonly [8, 12, 5, 8]'.
這裏發生的事情偏偏是文檔的要點:
payload 數組確實是 [8,12,5,8] 的「只讀」元組(不過我並無從文檔中看到這方面的說明)。
我用如下代碼總結以上全部內容:
1let obj = { 2 x: 10, 3 y: [20, 30], 4 z: { 5 a: 6 { b: 42 } 7 } 8} as const;
對應於:
1let obj: { 2 readonly x: 10; 3 readonly y: readonly [20, 30]; 4 readonly z: { 5 readonly a: { 6 readonly b: 42; 7 }; 8 }; 9};
在這裏,我能夠推斷出類型,而不是去編寫多餘的樣板類型。這對於 redux 特別有用。
原文:https://blog.logrocket.com/const-assertions-are-the-killer-new-typescript-feature-b73451f35802