iT邦幫忙

第 11 屆 iThome 鐵人賽

5
Modern Web

讓 TypeScript 成為你全端開發的 ACE!系列 第 51

Day 50+ 用了會上癮的 TypeScript 新功能 - Easily Addicted New Features in TypeScript

貼心小提示

筆者最近開始在整理內容並且使用 TypeScript 實驗並且寫各種小專案,因此 Day 50+ 以後的內容通常都是筆者偶爾會整理的一些很實用的東西給讀者看喔~

畢竟很多讀者 Sub 本串代表 TypeScript 在台灣的需求其實是有的,再加上 TypeScript 憑筆者才幾個月經驗來說覺得挺方便,後面還會分享自己寫的開源專案~

TypeScript 3.7 最新內容

雖然說 TypeScript 版本 3.7 是已經發布幾個月了,但是不免還覺得想跟讀者分享一些東西,因為這些 Feature 實在是太好用了。

ES10 Optional Chaining

讀者有沒有遇過一種情況是 —— 比如說函式裡通常會有所謂的設定相關的東西,通常用英文都是 optionconfig 來代表:

interface SomeConfig {
  a: TypeA;
  b: TypeB;
  c: {
    d: TypeCD;
    e: {
      f: TypeCEF;
      g: { /* ... */ }
    };
  };
};

想當然,以上的程式碼,如果我們要取出設定內部的屬性,就必須要很麻煩地使用一大堆 if 判斷敘述判斷設定之屬性是否存在,因此可能會長得像這樣:

function someFunc(param1, ..., config: SomeConfig) {
  if (config.a) { /* ... */ }
  if (config.b) { /* ... */ }
  if (config.c) {
    if (config.d) { /* ... */ }
    if (config.e) {
      if (config.f) { /* ... */ }
      if (config.g) {
        /* ... */
      }
    }
  }
}

亦或者是使用 && 串下去:

function someFunc(param1, ..., config: SomeConfig) {
  if (config.a) { /* ... */ }
  if (config.b) { /* ... */ }
  if (config.c && config.c.d) { /* ... */}
  if (config.c && config.c.e && config.c.e.f) { /* ... */}
  if (config.c && config.c.e && config.c.e.g) { /* ... */}
}

這樣實在是太麻煩,因此 ECMAScript 確實有 Proposal (名為 proposal-optional-chaining) 使用 ?. 這個串鏈運算子來簡化以上遇到的情形。事實上該 ES Proposal 是 2019 年初(筆者如果沒記錯的話)就已經到 Stage 4,嚴格來說代表它是 ES10 的特色,雖然已經正式釋出,但是因為也釋出快一年左右,所以目前大部分瀏覽器沒支援也是正常,通常都會用 Babel Compiler 來幫忙編譯。

但 TypeScript 於 3.7 版正式支援這個語法特色,其簡化結果如下:

function someFunc(param1, ..., config: SomeConfig) {
  if (config.a) { /* ... */ }
  if (config.b) { /* ... */ }
  if (config.c?.d) { /* ... */}
  if (config.c?.e?.f) { /* ... */}
  if (config.c?.e?.g) { /* ... */}
}

好,有些讀者可能剛學 JavaScript 就直接跳到 TypeScript 或還沒有很關注到 ECMAScript 的近期發展可能覺得這個 config.c?.d 寫法還是有點難轉過來,用口頭來說,意思是:

如果 config.c 的值為一個含有 d 屬性的物件,則呼叫 config.c.d

雖然說大部分文章都會講機制,不過縱貫本系列文的風格,這裡就要出給讀者幾個小問題:

讀者試試看

  1. 如果 config.c 的值為 undefinednull,則:config.c?.d 會出現什麼呢?
  2. 如果 config.c 的值為非物件的原始型別值,如數字 123 或字串 hello world,則:
  • config.c?.d 會出現什麼?
  • config.c 若為字串,則是不是代表 config.c?.[any]後面是不是可以接任何字串相關的方法,例如 reverse()toUpperCase() 等等
  1. 如果 config.c 的值為空物件 {},則 config.c?.d 會出現什麼?

ES10 Nullish Coalescing

讀者看的英文一定覺得很怪,Coalescing 這的動詞到底是啥?其實這是電腦科學專用名詞,代表為『 與...東西結合』的意思,也就是說 —— Nullish Coalescing 代表跟 Null 相關的東西結合的機制。

請讀者注意,是與 Null相關,所以不是只有 null 這個值而已,還包含 undefined

跟 Optional Chaining 相似,Nullish Coalescing 也出自於某個 ECMAScript 官方的 Proposal (proposal-nullish-coalescing),並於去年就已經升到 Stage 4,所以被筆者自個兒歸類為 ES10 的標準。(實際上筆者是根據正式升為 Stage-4 的官方 Commit 的時間為標準;意思是說,如果該 Proposal 被正式升級為 Stage-4 的時間是 2019 年,則筆者認為他就是 ES2019 —— 亦即 ES10 的標準,但精確一點應該是要翻官方文件,不過筆者目前懶得看

相信使用過 JavaScript 一段時間的讀者,會發現使用 &&|| 運算子不全然會回傳布林值 truefalse,但讀者可能會問:

恩!?那 if...else... 內部是怎麼處理那些判斷式的?(提示:toBoolean,或者是自己找書看,推薦《0 陷阱!0 誤解!8 天重新認識JavaScript!》,相信大部分看過鐵人賽文章都知道這本知名書籍~筆者不再多說)

由於上面的那個問題並不在本篇討論範圍,因此就請讀者自行查查囉~

主要是:有一種名為短路語法(Short Circuiting)的技巧,意思是這樣 —— 假設我們想要設定某個變數的值,但是該變數若為空,則需要預設(Default)另一個值:

let message = option.message;

if (!message) {
  /* 若 message 為空值 undefined,則預設為 'Hello World' */
  message = 'Hello World';
}

(以上的語法前提是,我們很確定 option.message 型別為 string | undefined

但是這樣的寫法實在麻煩,所以有一種短路的語法可以將上面的程式碼簡化成:

let message = option.message || 'Hello World';

意思是,如果 option.message 代表的值是 JS 裡偏向於 Truthy 的樣貌,則 message 就會被指派為 option.message 這個值;相反地,若 option.message 代表的值是 JS 裡偏向於 Falsey 的樣貌,則 message 就會指派為 'Hello World' 這個預設值

好,熟悉 JavaScript 的讀者一定知道,什麼是指偏向於 Truthy 或 Falsey 樣貌的值?

以下為代表偏向於 Falsey 的值:

  • 布林值 false
  • null
  • undefined
  • 數字 0NaN
  • 空字串 ''

也就是說,剛剛的程式碼:

let message = option.message || 'Hello World';

如果 option.message 被指派為 —— 假設是空字串 ''message 也會被自動短路為 'Hello World'這個值。但是,如果我們想要的行為是:message 也可以是空字串的話,那麼以上的寫法就錯了!必須改寫為:

let message = option.message || (option.message === '' ? '' : 'Hello World');

意思是:如果 option.message 是空字串,我們也會將空字串的值指派給 message 變數,又可以防止其他 Falsey 的值被指派到 message 變數裡。

哇,不過這樣的寫法也是挺麻煩的,所以也就是說,如果我們想要預設數字,可能也會這麼做:

let someNum = option.num || (option.num === 0 ? 0 : option.num);

(以上的語法前提是,我們很確定 option.num 型別為 number | undefined

這時候,ES10 Nullish Coalescing 的技巧出場了 —— 它的運算子看起來有點好笑,就是兩個問號 ?? —— 以上的 option.message 指派方式的範例可以被簡化為:

let message = option.message ?? 'Hello World';

?? 只會擋兩個東西:nullundefined,這也是筆者在剛剛有稍微強調過:

並不是只有 null,是跟 Null 相關的東西,也就是 nullundefined

Nullish Coalescing 英文是 Nullish —— 是形容詞,不是 Null Coalescing,再次強調不是 Null Coalescing,是 Nullish Coalescing!

所以上面的程式碼等效於:

let message = (option.message !== null && option.message !== undefined) ? option.message : 'Hello World';

這個語法用在預設值方面的技巧非常好用的說,但就一開始可能還是很多問號臨時看不懂,但用久習慣成自然。

其他的 Feature

其實 TypeScript 3.7 發佈的其他 Feature 稍微看過之後,筆者會想講 Assertion Function (斷言式)的部分,但考慮會加入到未來幾個月出的書的內容中,因為會跟 Type Guard 型別檢測的東西還蠻相關的,而正確的使用斷言式也可以改善 Debug 的效率,這也是書中會想要講的東西。(但筆者還在寫稿

另外,會比較建議如果是只有來自前端的背景但沒有使用或很深入 NodeJS 的經驗的讀者,可以嘗試一下 Assertion Function,不過測試相關的內容如果筆者有空的話也可以分享一下,有太多事情可以寫

以下內容為筆者推廣自己的開源專案,但想要看看 TypeScript 寫出的開源專案,可以參考以下內容喔~!

筆者最近寫的專案 —— Wyrd Lang

其實筆者最近也在試著用 TypeScript 寫過一個比較大的開源專案,畢竟累積一些經驗就可以主動提出些寫的過程可能會遇到的困難並記錄起來,並且推廣一下自己寫的東西 XDDD

可能有些讀者看到 Wyrd 這個字覺得很奇怪 —— 沒錯,它是英文的 Weird (古怪的)的古字前身,Repo 裡面的 README 有更詳細的說明

Wyrd Lang 主要是一個語言,想要走偏 Functional Programming 和借鏡一些 Ruby Lang 的語法,編譯出相對應的 JavaScript 程式碼。另外,筆者希望所有的東西都是表達式(Expressions),代表所有的程式碼都會回傳值

貼心小提示

敘述式(Statement)簡單來說是一種結構,並不會回傳值,例如判斷敘述式或重複敘述式:

// 判斷敘述式
if (/* ... */) else { /* ... */ }

// 重複敘述式
for (/* ... */) { /* ... */ }

表達式(Expressions)口語化來說是一種運算過程,理應當然會回傳值,例如運算或邏輯表達式:

// 運算表達式
(1 + 2) * 3 - 4

// 邏輯表達式
a > b || (c < d && e)

不過短短的一個禮拜內,目前大概有可以簡單編譯過後的成果(但主體 Compiler 本身還沒出來 XDD);以下是幾段 Wyrd Lang 對應編譯過後的 JS 語法程式碼:

  1. 基本的運算式與指派式

Wyrd 程式碼:

foo = 1 + (2 - 3) * 4

編譯過後:

const foo = 1 + ((2 - 3) * 4);
  1. 邏輯表達式

Wyrd 程式碼:

not (False or True) and False
8 / (4 * 2) > 3 and not 1 + 2 * 3 == 7 or a + b / c * d != w - x * y

編譯過後:

!(false || true) && false;
8 / (4 * 2) > 3 && !(1 + (2 * 3) === 7) || a + (b / c * d) !== w - (x * y);
  1. 判斷表達式(請注意:是表達式,代表以下的 if...then...if... => 會回傳值)

Wyrd 程式碼:

example1 = if age < 18    => "youngster"
           elif age <= 60 => "adult"
           elif age < 100 => "elder"
           else           => "centenarian"


example2 = if age < 18 then
             "youngster"
           elif age <= 60 then
             "adult"
           elif age < 100 then
             "elder"
           else then
             "centenarian"
           end

編譯過後:

const example1 = age < 18 ? 'youngster' : (age <= 60 ? 'adult' : (age < 100 ? 'elder' : 'centenarian'));
const example2 = age < 18 ? 'youngster' : (age <= 60 ? 'adult' : (age < 100 ? 'elder' : 'centenarian'));
  1. 函式宣告

Wyrd 程式碼:

def addition(x: Num, y: Num): Num => x + y
def complexArithmetic(w: Num, x: Num, y: Num, z: Num): Num do
  a = x + y * z
  b = w - 2 / a + 1
  b
end

編譯過後:

function addition(x, y) {
  if (typeof x === 'number' && typeof y === 'number') {
    return x + y;
  }

  throw new Error('Wrong Parameter Type in function \`addition\`');
}

function complexArithmetic(w, x, y, z) {
  if (typeof w === 'number' && typeof x === 'number' && typeof y === 'number' && typeof z === 'number') {
    const a = x + (y * z);
    const b = w - (2 / a) + 1;
    return b;
  }

  throw new Error('Wrong Parameter Type in function \`complexArithmetic\`');
}

但最近由於筆者自己開始會在編譯過程中紀錄型別內容等等,未來應該會編譯成更單純的樣子:

function addition(x, y) {
  return x + y;
}

function complexArithmetic(w, x, y, z) {
  const a = x + (y * z);
  const b = w - (2 / a) + 1;
  return b;
}

貼心小提示

通常編譯器的角色是要在編譯過程中檢測語法錯誤,因此在函式的宣告部分,筆者之所以會改成更簡單的形式的原因在於:如果編譯出的結果還要讓 JavaScript 再做一次型別檢測,豈不是很諷刺的一件事情?

另外,如果單純只是翻譯 Wyrd 到 JavaScript 的話,那這個 Wyrd Lang 應該比較偏向於 Interpreter 或者是 Transpiler 的概念,就是單純轉譯,而型別等內容都是由 JavaScript 來處理。

以上大概是筆者目前 Wyrd-Lang 開源專案初步的進展 XDD

另外,有興趣或者想要一起精進 TypeScript 的讀者,亦或者是想要試著在 GitHub 上貢獻並且放在履歷經歷的讀者們,筆者誠心歡迎大家去 Maxwell-Alexius/Wyrd-Lang 可以開 Issue 討論甚至是想要送 PR 都可以~(P.S. 寫測試是一種很好的起手式唷!)

其實寫過一次編譯器過後,覺得當時發明 JavaScript 的 Brendan Eich 真的還蠻強的,可以短短時間內 Prototype 出一個語言,自己還有很多東西還可以在精進。

未來的文章應該也會分享自己寫專案中遇到的種種狀況與心得~


上一篇
Day 50. 通用武裝・非同步函式X非同步程序的同步化-TypeScript Generics with Asynchronous Programming III. Async Functions
系列文
讓 TypeScript 成為你全端開發的 ACE!51
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

2 則留言

0
nihilitypeo
iT邦新手 4 級 ‧ 2020-01-27 17:26:06

推推!
期待書的到來

0
阿展展展
iT邦好手 1 級 ‧ 2020-02-15 05:29:39

太強大了 好恐怖的文字量
恭喜完賽

我要留言

立即登入留言