Typescript 3 使用,那些奇怪的符號: operators, private, decorator, generic type

本篇會比較強調 typescript 中一些特殊符號,還有>ES6的一些符號,例如說optional chainning,private等的特殊符號。說實話,這篇真的覺得難寫,我不該把範圍開那麼大,不過常用的特殊符號分開來就感覺凌亂,還是寫在一篇我覺得比較好...吧?

| union type 的使用

Union type 使用的時候,即代表參數同時接受 | 兩邊的型別定義,但是注意,他只能符合其中一個型別

let strOrNumber: string | number | undefined; // 同時接受 string, number, undefined的型別

進階使用方式我常用在撰寫 function 中若有不同的 state,代入的argument也會不同

// 通常出現在 react reducer 的 action 上

type IAction = {
    type: 'init' // 這些參數甚至可以搭配enum操作
} | {
    type: 'update',
    payload: {
        arg: string
    }
} | {
    type: 'disable',
    payload: {
        isShow: boolean;
    }
}

function reducer(state = initialState, action: IAction) {
    // do content
    switch (action.type) {
        case 'init':
            // no payload
            break;
        case 'update':
            action.payload.arg // string type
            break;
        case 'disable':
            action.paylaod.isShow // boolean type
            break;
    }
}

& intersection types 的使用

這個使用是將兩者 object 結合在一起即為Object.assign的功能,不過是在typescript定義型別中才會用到的屬性。

個人使用通常會先定義個基礎型別,再基於它做擴充

type IBasicType = {
    id: number;
    value: string;
}

type IExtend = IBasicType & {
    foo: string;
}

? 和 ! 的使用 optional type assertion non-null assertion

?在不同的地方有不同的用處,在 typescript 定義檔中,如果看到以下的宣告方式,就是代表此值為 optional ,可能存在或者是 undefined

interface IFoo {
    bar?: string;
}

function getFoo (payload: IFoo) {
    const bar:string = payload.bar; // 這裡會噴錯誤,因為payload.bar可能是undefined
}

如果是搭配 if / else typescript 能夠有辦法自行在該 block 推斷型別

function getFoo (payload: IFoo) {
    if (payload.bar !== undefined) {
        // 這裡即會將payload.bar推斷為string type
    }
}

如果在實作中已經 100% 確定接下來的值不是 undefined 可是卻無法被 typescript 正確推斷,這類型情況比較常發生在 MapArray.prototype.find 上,通常自行設定的會是1:1的對應關係可是一直會有錯誤顯示說可能為undefined 這時候有幾種解法。

先以我常用的Map型別做個簡易的示範

const map = new Map([
  [1, { foo: true }],
  [3, { foo: false }],
]);

1的值

const first = map.get(1);

此時可以看到顯示的type型別會如下

const first: {
    foo: boolean;
} | undefined;

這樣在後續進行實作上又要為了無謂的型別確認浪費行數做這件事,這樣的話我會用以下2種解決辦法

  1. type assertion 類型斷言直接強制型別
const first = map.get(1) as { foo: boolean }
  1. 直接使用 Non-null assertion operator
const foo = map.get(1)!

而在 typescript 3.7版本中新增當前為 tc39/stage 4 的 optional chainning

map.get(1)?.foo // undefined or boolean
map.get(1)?.['foo'] //亦可,如果是array就改number

在這邊使用?即代表該值可能為 undefined 而在此就會中斷避免後續執行造成error,而除了可以對value進行選擇外,對於執行 function 的話可以改成如下

const map = new Map([[1, function () { return true; }]);

map.get(1)?.() // 當確定有值才會執行function的內容

相關處理在 Aray.prototype.find也會出現,如何使用就看當下情景做選擇了。

# private fields

這個是當前已經進入tc39-stage3 的功能,未來不久就會出現的規範,早前 typescript 為了 private 有直接取用這個關鍵字來宣告是 private field 即不可被外部存取。現在出現#的關鍵字就直接引用了

class Foo {
    #bar: string;
    constructor () {
        this.#bar = 'bar';
    }
}

const foo = new Foo()
foo.bar // get Error here

一樣,現在寫js都比較偏向functional programming,這個private型別對我來說已經算不太常會用到的功能了

@ decorators

裝飾器,僅能使用於class的項目之一,這個解釋起來略複雜,有機會後面再說(明明就是不會用)

Generic Type: 泛型 <T>

使用上通常是拿來做爲有可擴充功能的型別來使用,使用泛型的好處就是避免在最初定義時就寫死他能夠使用的型別,讓使用該funciton的地方能夠自行決定其中的T型別爲何。

具體例子

沒有泛型的情況,我們只能以特定型別設定

function identity(arg: number): number {
    return arg;
}

這樣只能限定identity使用number傳入,可是其回傳的並不只能number可以使用。OK,那就改用any

function identity(arg: any): any {
    return arg;
}

那這樣就將ts的好處無視掉了,後續接手的也會不清楚return會如何,只能花費時間深入研究。這時就可以使用泛型解決這個問題

function identity<T>(arg: T): T {
    return arg;
}

這樣在使用上,我可以清楚的知道回傳會有什麼東西,或者一開始就先預設好應該有的型別

identity<string>('mystring');

如此一來,我就能設定好該項僅能接收string這個型別的東西而不用另外建立一個function處理型別問題。

除了function外 interfacetypeclass 皆可使用泛型喔

// interface
interface IFoo<T> {
    bar: T;
}
// type
type TFoo<T> = { bar: T }
// class
class Foo<T> {
    private bar: T;
}

簡單就是這樣,接下來講實務用法。

泛型可以直接針對需要的型別做擴充,或者直接預設可以繼承相關的型別,抑或是搭配string提示,後面可以採用string literal的宣告方式

  1. 擴充型別

使用extends來做繼承/擴充

function identity<T extends { search: string }>(arg: T) {}

// 使用就必須使用search且型別為string的object了
identity({search: 'a', bar: 'ok'});
  1. 預設型別,另外function 使用搭配string literal實現
// 這樣就不會強迫需要設定generic type
function identity<T extends string = ‘’>(terms: T) {}

type TAllowToUse = 'Foo' | 'Bar';

identity<TAllowToUse>('Foo');
identity('I can do every thing');
  1. Axios回傳Data預設型別

現在使用的ajax library已經習慣使用Axios了,後續在實作上套用的時候,request回來的資料型別都是any,Axios 有提供泛型讓使用者加入回傳 type

const response = await Axios.get<{hello: string}>('url').then(res => res.data); // res.data就是{hello: string}的型別
  1. interface使用
interface IFoo<T> {
    // extends use intersection
    bar: T & { search: string };
} 

泛型使用很靈活,但是也很容易濫用,就看使用者如何設計了。


下一篇 Typescript 4 那些奇怪的字:extends, infer, typeof

留言

這個網誌中的熱門文章

ts-node 應用

ubuntu 日常(X)紀錄

Angular 2 with Third Party files