この記事は、ベーシック社内で毎月開催している TGIF という LT 大会で発表した内容です。前回に続き今回もオンライン Zoom での開催になりました。オンラインでやる LT はいつもと違って新鮮ですね。個人的にはチャットでみんなの反応が見られるので話しやすかったです。

TypeScript 型入門の入門

TypeScript を使った型の入門の入門です。TypeScript をまだ書いた事がない人向けです。

let hoge: string
hoge = 'hoge' // Good
hoge = 100    // Bad: Type '100' is not assignable to type 'string'.

変数名の隣にコロンをはさんで型名を書けば型定義ができます。

変数名: 型名

常に型を描かなくても代入時に型を推論してくれます。

let hoge = 'hoge'
hoge = 100 // Bad: Type '100' is not assignable to type 'string'.

関数の引数や戻り値にも型を指定できます。

function doSomething(arg: string): string {
  return arg.toUpperCase()
}
doSomething('zaru') // Good: return ZARU
doSomething(100)    // Bad: Argument of type '100' is not assignable to parameter of type 'string'.

独自の型を定義することもできます。これはプロパティを定義した独自のオブジェクト型です。

type TMyType = {
  name: string,
  echo: Function
}

const my: TMyType = {
  name: 'name',
  echo() { /* do something */ }
}

配列を表現することもできます。

const ids: number[] = [1, 2, 3]
ids.push('string') // Bad: Argument of type '"string"' is not assignable to parameter of type 'number'.

具体的な値を示す型を定義することもできます。これはリテラル型と言います。

let foo: 'foo' = 'foo'
foo = 'bar' // Bad: Type '"bar"' is not assignable to type '"foo"'.

また、 | で型を区切ることで複数の型を取りうる定義ができます。これをユニオン型と言います。

type TOrderBy = 'asc' | 'desc'
const orderBy: TOrderBy = 'asc'     // Good
const orderBy: TOrderBy = 'default' // Bad: Type '"default"' is not assignable to type 'TOrderBy'.

as を使って、変数を後から指定の型として表明(Type assertions)することもできます。

const my = {
  name: 'name',
  echo() { /* do something */ }
} as TMyType

問題です

第1問

以下を実行したとき、どのような出力が得られるでしょうか?

let str: string = 'string'
let num: number = 100
str = num
console.log(str)

回答

Error: Type 'number' is not assignable to type 'string'.

第2問

以下を実行したとき、どのような出力が得られるでしょうか?

let str: string = 'string'
let num: number = 100
let res = str + number
console.log(res)

回答

string100

第3問

以下を実行したとき、どのような出力が得られるでしょうか?

const foo:'foo' = 'foo'
function modifyStr(str: string) {
  console.log(str.toUpperCase())
}
modifyStr(foo)

回答

FOO

第4問

以下を実行したとき、どのような出力が得られるでしょうか?

let str1: string = 'string'
let str2: String = 'new string'
str1 = str2
console.log(str1) // ???

回答

Error: Type 'String' is not assignable to type 'string'. 'string' is a primitive, but 'String' is a wrapper object. Prefer using 'string' when possible.

第5問

以下のような型と関数があります。

type TZaru = {
  name: string
}
type TTofu = {
  name: string,
  size: number
}

function checkZaru(zaru: TZaru) {
  console.log(zaru.name)
}

以下のように関数を実行するとき、どのような出力が得られるでしょうか?

const tofu: TTofu = {
  name: 'tofu',
  size: 100
}
checkZaru(tofu) // ???

checkZaru({ name: 'tofu', size: 100 }) // ???

回答

tofu

Error: Argument of type '{ name: string; size: number; }' is not assignable to parameter of type 'TZaru'. Object literal may only specify known properties, and 'size' does not exist in type 'TZaru'.

第6問

この関数は number の引数を取ります。number 以外の値を渡すとコンパイルエラーになります。完全に型がチェックされています。しかし doSomething は完全に安全ではありません。どのような場合に安全ではないでしょうか。

function doSomething(arg: number): number {
  return arg * 100
}
doSomething('string') // Bad
type TOrderBy = 'asc' | 'desc'
function changeOrder(orderBy: TOrderBy) {
    console.log(orderBy)
}
changeOrder('default') // Bad

回答

外部から値を与えられたケースだと型チェックはすり抜けてしまいます。

(async()=> {
  function doSomething(arg: number): number {
    return arg * 100
  }
  const result = await fetch('/api')
  const json = await result.json()
  doSomething(json.id)
})()
const button = document.getElementById('button')
button?.addEventListener('click', () => {
    changeOrder(button.dataset.orderby as TOrderBy)
})

例えば fetch で API からデータを取得する TypeScript のコードをコンパイルして JavaScript にするとこうです。

(async () => {
  function doSomething(arg) {
    return arg * 100;
  }
  const result = await fetch('/api');
  const json = await result.json();
  doSomething(json.id);
})();

型の定義が消えているだけで、何か変数の型チェックをしているわけではありません。TypeScript の型はあくまで既存の JavaScript 構文に型アノテーションをつける事ができる拡張です。つまり実行時には型の保証をしてくれるわけではありません。型アノテーションがあるところだけ可能な限り型チェックをするが、そうでない場合は型チェックはしないという動きになります。そのために any 型というものが存在します。

(TypeScript に限った話ではなく他の言語でも起きる現象です)

今回のケースで言うと json という変数は any 型です。any はなんでも OK なので doSomething に渡しても通ってしまいます。TypeScript だから型チェックしてくれて安全〜と盲目的に思わずに、外部からの入力値は必要に応じてきちんと検査するようにしましょう。