Programming TypeScript ch3 (2/2)

TypeScript勉強メモ

出典: 


Intermission: Type Aliases, Unions, and Intersections

  • 値に対して操作ができる

    • number に対する+
    • string に対する.toUpperCase()
  • 型に対しても操作ができる

Type aliases

type Age = number
type Person = {
    name: string
    age: Age
} // { name: string; age: number; }

let age: Age = 55 // number

let driver: Person = {
    name: 'John',
    age: age
} // Person
  • 型エイリアスはブロックスコープ
type Color = 'red'

let x = Math.random() < 0.5

if (x) {
    type Color = 'blue' // 上のColor定義を隠す
    let b: Color = 'blue'
} else {
    let c: Color = 'red'
}

Union and Intersection Types

type Foo = {
    foo: boolean
    baz: boolean
}
type Bar = {
    bar: boolean
    baz: boolean
}

type FooOrBarOrBoth = Foo | Bar;

type FooAndBar = Foo & Bar;


let a: FooOrBarOrBoth

a = {
    foo: true,
    baz: true
}

a = {
    bar: true,
    baz: true
}

a = {
    foo: true,
    bar: true,
    baz: true
}

a = {
    baz: true
} // Error TS2322


let b: FooAndBar

b = {
    foo: true,
    bar: true,
    baz: true
}

b = {
    foo: true,
    baz: true
} // Error TS2322

b = {
    bar: true,
    baz: true
} // Error TS2322
  • |は「どちらか片方」ではない

    • 両方の集合の要素であってもよい
    • 共通部分がなく、どちらか片方であるようなもの: 「Discriminated Union Types」
  • 【補】FooAndBar型に一度受けてからFooに入れるのはOK、オブジェクトリテラルをそのまま入れるのはエラー
let b: FooAndBar = {
    foo: true,
    bar: true,
    baz: true
}
let c: Foo

c = b; // OK

c = {
    foo: true,
    bar: true,
    baz: true
} // Error TS2322: Type '{ foo: true; bar: boolean; baz: true; }' is not assignable to type 'Foo'.
  • Union Typesのほうがよくお目にかかる
function or (a: string, b: number) {
    return a || b
} // returns string|number

Arrays

let a = [1,2,3]   // number[]
let b = []        // any[]
let c = [2,'a']   // (string | number)[]
const d = [2,'a'] // (string | number)[]

a.push('b') // Error TS2345: Argument of type '"b"' is not assignable to parameter of type 'number'.
b.push('b')
c.push('b')
  • 要素型は混ぜないほうがいい
  • オブジェクト同様、constにしても特に型が狭まったりしない

Tuples

  • 明示的に型宣言する必要がある

    • JSのシンタックス上、配列とタプルの区別がないため配列に推論されてしまう
let a: [number] = [1]
a = [] // Error TS2741: Property '0' is missing in type '[]' but required in type '[number]'.

let b: [string,string] = ['Martin', 'Fowler']
b = ['Robert', 'C', 'Martin'] // Error TS2322: Type '[string, string, string]' is not assignable to type '[string, string]'.
  • オプショナル要素
type PairOrTriple = [number, number, number?]
type PairOrTriple2 = [number,number]|[number, number, number]


let a: PairOrTriple[] = [
    [1],       // Error TS2741: Property '1' is missing in type '[number]' but required in type 'PairOrTriple'.
    [1,2],
    [1,2,3],
    [1,2,3,4], // Error TS2322: Type '[number, number, number, number]' is not assignable to type 'PairOrTriple'.
]

let b: PairOrTriple2[] = [
    [1],       // Error TS2322: Type '[number]' is not assignable to type 'PairOrTriple2'.
    [1,2],
    [1,2,3],
    [1,2,3,4], // Error TS2322: Type '[number, number, number, number]' is not assignable to type 'PairOrTriple2'.
]
  • 可変長
type RestSample = [number, boolean, ...string[]]

let restSample: RestSample[] = [
    [1,true],
    [1,true, 'hoge'],
    [1,true, 'hoge', 'fuga'],
]

Read-only arrays and tuples

let as: readonly number[] = [1,2,3]
as.push(4);   // Error TS2339: Property 'push' does not exist on type 'readonly number[]'.
as.reverse(); // Error TS2339: Property 'reverse' does not exist on type 'readonly number[]'.

let bs = as.concat(4); // number[]
bs.push(5);
bs.reverse();
  • 長い書き方
type A = readonly string[]
type B = ReadonlyArray<string>
type C = Readonly<string[]>
type D = Readonly<Array<string>>

type E = readonly [number, string];
type F = Readonly<[number, string]>

null, undefined, void, and never

  • 【所感】言うほど似てない
// returns number | null
function a(x: number) {
    return x < 10 ? x : null;
}

// returns undefined
function b() {
    return undefined
}

// returns void
function c() {
    console.log('hola')
}

// returns never
function d() {
    throw TypeError('always error');
}

// returns never
function e() {
    while (true) {
        // do something
    }
}

function f(x: 1 | 2 | 3) {
    switch (x) {
        case 1:
            x; // 1
            break;
        case 2:
            x; // 2
            break;
        case 3:
            x; // 3
            break;
        default:
            x; // never
    }
}
  • never: ボトム型

    • 他のどの型にも代入可能

Column: strict null checking

  • 昔のTSではnullnever以外のボトム型だった
  • すべてがnullableだったということ

    • nullチェックしないと安全でない
    • 怠ると実行時例外が送出されうる

Enums

enum Language {
    English, // 0
    Spanish, // 1
    Russian  // 2
}

enum Color {
    Red = '#c10000',
    Pink = 0xc10050,
    White = 255
}

let a = Color.Red   // Color
let b = Color.Green // Error TS2339: Property 'Green' does not exist on type 'typeof Color'.
let c = Language[0] // string
let d = Language[3] // string (!!!)
  • C言語のenumと同じ感じに自動採番される
  • 値でのアクセス(reverse-lookup)は危険
  • 【補】出力されるJSコード
"use strict";
var Language;
(function (Language) {
    Language[Language["English"] = 0] = "English";
    Language[Language["Spanish"] = 1] = "Spanish";
    Language[Language["Russian"] = 2] = "Russian"; // 2
})(Language || (Language = {}));
var Color;
(function (Color) {
    Color["Red"] = "#c10000";
    Color[Color["Pink"] = 12648528] = "Pink";
    Color[Color["White"] = 255] = "White";
})(Color || (Color = {}));
let a = Color.Red; // Color
// let b = Color.Green // Error TS2339: Property 'Green' does not exist on type 'typeof Color'.
let c = Language[0]; // string
let d = Language[3]; // string (!!!)
//# sourceMappingURL=index.js.map
  • 危険な値でのアクセスを禁止するには、const enumを使う
const enum Language {
    English, // 0
    Spanish, // 1
    Russian  // 2
}

const enum Color {
    Red = '#c10000',
    Pink = 0xc10050,
    White = 255
}

let a = Color.Red   // Color
let c = Language[0] // Error TS2476: A const enum member can only be accessed using a string literal.
let d = Language[3] // Error TS2476: A const enum member can only be accessed using a string literal.
  • 出力されるJSコード

    • C++11でいうところのconstexprな感じですね
"use strict";
let a = "#c10000" /* Red */; // Color
// let c = Language[0] // Error TS2476: A const enum member can only be accessed using a string literal.
// let d = Language[3] // Error TS2476: A const enum member can only be accessed using a string literal.
//# sourceMappingURL=index.js.map
  • const enumの利用
const enum Language {
    English,
    Spanish,
    Russian
}

function greet(lang: Language) {
    // stub
    return 'hello';
}

greet(Language.English)
greet(Language.Spanish)
greet(Language.Russian)
greet(99)  // コンパイルが通ってしまう
  • 出力されるJSコード
"use strict";
function greet(lang) {
    // stub
    return 'hello';
}
greet(0 /* English */);
greet(1 /* Spanish */);
greet(2 /* Russian */);
greet(99); // コンパイルが通ってしまう
//# sourceMappingURL=index.js.map
  • 値がnumberのconst enum型には任意のnumberを代入できてしまう…
  • 回避するには、値を文字列にする
const enum Language {
    English = 'English',
    Spanish = 'Spanish',
    Russian = 'Russian'
}

function greet(lang: Language) {
    // stub
    return 'hello';
}

greet(Language.English)
greet(Language.Spanish)
greet(Language.Russian)
greet('Deutsch') // Error TS2345: Argument of type '"Deutsch"' is not assignable to parameter of type 'Language'.
  • TSのenumは罠がいっぱいなので使わないこと推奨

    • TSでは他にもっとよい表現方法がいろいろある
  • 頑なにnumeric-valueなenumを使う同僚がいたら、non-const enumやnumeric valueに警告を出すTSLintルールをこっそりマージ(ninja-merge)しよう

Column: TCS Flag: preserveConstEnums

  • tsconfigでpreserveConstEnumstrueにすると、const enumでもオブジェクトを生成するようになる

tsconfig.json

{
  "compilerOptions": {
  ...
    "preserveConstEnums": true
  },
...
}
"use strict";
var Language;
(function (Language) {
    Language[Language["English"] = 0] = "English";
    Language[Language["Spanish"] = 1] = "Spanish";
    Language[Language["Russian"] = 2] = "Russian"; // 2
})(Language || (Language = {}));
var Color;
(function (Color) {
    Color["Red"] = "#c10000";
    Color[Color["Pink"] = 12648528] = "Pink";
    Color[Color["White"] = 255] = "White";
})(Color || (Color = {}));
let a = "#c10000" /* Red */; // Color
// let c = Language[0] // Error TS2476: A const enum member can only be accessed using a string literal.
// let d = Language[3] // Error TS2476: A const enum member can only be accessed using a string literal.
//# sourceMappingURL=index.js.map

Summary

  • TSには型がいっぱい
  • const だとよりspecificな型が推論される
Type Subtype
boolean Boolean literal
bigint Bigint literal
number Number literal
string String literal
symbol unique symbol
object Object literal
Array Tuple
enum const enum

Exercises

let h = null // any (!!!)

英語

  • intermission

    • 休憩
  • grizzled

    • 白髪まじりの灰色の頭をした
    • グリズリー(ハイイログマ)