🐮

【TypeScript】初歩的だけど忘れがちな構文

2021/07/25に公開

TypeScriptの基本的な所は知っているけど、「あれの呼び方何って言うんだっけ?」とか「interfaceとtypeって何が違うんだっけ?」となりがちなので、その辺りをまとめてみました。

1. 基本

覚えておきたいTSの基本的な構文や型

インデックスシグネチャ

[key: T]: Uという構文をインデックスシグネチャを呼びます。インデックスシグネチャのキーの型(T)は、numberかstringのどちらかでなければいけません。

interface NumberList {
  [key: number]: string
}
const a: NumberList = { 1: 'one', 2: 'tow' }

a[3] = 'three'
console.log(a) //  { "1": "one", "2": "tow", "3": "three"}

タプル型

タプル型は配列の派生型です。固定長の配列を型付けするための型です。

const a: [number] = [1]
const b: [number, string] = [1, 'foo']
const c: [number, string] = [1, 'foo', 'bar'] // Error

タプル型を可変長にしたい場合は、スプレッド構文を使います。

const a: [number, ...string[]] = [1, 'a', 'b']

列挙型

キーが固定されているオブジェクトのようなものです。列挙型はenumとも言います。しかし、列挙型の利用は、非推奨と言われています。その理由は「3. Tips」で後述します。

const enum Color {
  Red = '赤',
  Bule = '青',
  Green = '緑'
}
console.log(Color.Green) // 緑

合併型、共用型、共用体型、union型(|)

AとBという2つがある場合、A, B, ABを指します。

type A = string | number

交差型(&)

AとBという2つがある場合、ABを指します。合併型、交差型ともにベン図を使うと理解しやすいです。

type A = string & number

オプションパラメーター(?)

パラメーターを省略可能にします。

type A = { 
  a?: number
}
const a: A = {}
console.log(a) // {}

非nullアサーション演算子(!)

非nullアサーション演算子は、その値が非null且つ、非undefinedであるとTSに伝えます。

interface User { name : string }
const user = { name: 'yamada' }

const getUser = (user?: User) => {
  console.log(user!.name) // "yamada"
}
getUser(user)

非nullアサーション演算子は、強制的にnullまたはundefinedでないことに出来てしまうため危険です。そのため、if文で存在をチェックしてあげると良いです。

nterface User { name : string }
const user = { name: 'yamada' }

const getUser = (user?: User) => {
  if (user && user.name) {
    console.log(user.name) // "yamada"
  }
}
getUser(user)

オプショナルチェイニング

オブジェクトのプロパティで、nullやundefinedのチェックに「?」を使うことで、プロパティがnullかundefinedの場合は、次のプロパティにはアクセスせず、undefinedを返します。

対応前

type Foo = {
  bar?: {
    baz?: string
  }
}

const foo: Foo = {
  bar: {
    baz: "hoge"
  }
}

const getHoge = (foo: Foo) => {
  if (foo.bar && foo.bar.baz) {
    console.log(foo.bar.baz)
  }
}
getHoge(foo)

対応後

const getHoge = (foo: Foo) => {
  if (foo.bar?.baz) {
    console.log(foo.bar.baz)
  }
}

https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html

2. 比較

概念が似たもの同士の比較

null, nudefined, viod, neverの違い

(1)null ... 値が欠如していることを意味します。

(2)nudefined ... あるものがまだ定義されていないことを意味します。

(3)viod ... 明示的に何も返さない関数の型です。

const a = () => {
  const b = 1 + 1
}

(4)never ... 決して戻ることのない関数の型です。例外とスローしたり、whileで永久的に処理を実行するとnever型になります。

const a = () => {
  throw new Error('Error')
}
const b = () => {
  while(true) {
    doSomething()
  }
}

object型とObject型の違い

もしオブジェクト型を使う場合は、Object型はなるべく避けてください。Object型は、string型やnumber型を許容します。

const a: Object = 'a'
const b: object = 'a' // Type 'string' is not assignable to type 'object'.(2322)
const a: Object = 1
const b: object = 1 // Type 'number' is not assignable to type 'object'.(2322)

interfaceとtypeの違い

interfaceとtypeの挙動は似ていますが、用途はinterfaceはクラスやオブジェクトの規格を定義し、typeは型や型の組み合わせに別名を付ける時に使います。

違いは以下のようなものがあります。ちなみにtypeは、型エイリアスと呼ばれます。

(1)型エイリアスは型演算子(&や|)を使うことが出来ますが、インターフェースは使えません。

type a = number | string
const a = 1

(2)同じ名前のインターフェースが存在する場合、宣言のマージが行われます。これは型エイリアスではエラーになります。

interface User {
  name: string
}
interface User {
  age: number
}

const User = {
  name: 'tanaka', age: 20
}
type User =  { // Duplicate identifier 'User'.(2300)
  name: string
}
type User =  {  // Duplicate identifier 'User'.(2300)
  age: number
}

const User = {
  name: 'tanaka', age: 20
}

interfaceとtypeの違いは、こちらにより詳しく載っています。

https://qiita.com/tkrkt/items/d01b96363e58a7df830e

3. Tips

覚えておきたい、ちょっとしたコツなど

number型で大きな値を扱う

number型で大きな数値を扱う時は、数字の区切りを使って読みやすくすることが出来ます。

const a: number = 3_000_000
console.log(a) // 3000000

anyよりもunknownを使う

unknownはanyと似ていますが、anyはどのようなデータもチェックなしで入れることができます。unknownの場合、変数を利用するにはtypeofを使って、チェックを行わないとエラーになります。そのため、本当に型が分からない値がある場合には、anyではなく代わりにunknownを使ってください。

let a: unknown = 30
let b = a + 10 // Object is of type 'unknown'.(2571)

let c = 0
if (typeof a === 'number') {
    c = a + 10
}
console.log(c) // 40

列挙型(Enum)の利用は非推奨

TSではEnumの利用は推奨されていません。例えば、Enumを使った次のコードのように関数の引数に数値を渡した場合でも、コンパイルエラーにならず、型安全ではなくなります。

const enum Fruit { Apple, Banana, Cherry }

const getFruit = (fruitName: Fruit) => {
  switch(fruitName) {
    case Fruit.Apple: return 'りんご'
    case Fruit.Banana: return 'ばなな'
    case Fruit.Cherry: return 'さくらんぼ'
    default: return ''
  }
}

console.log(getFruit(6)) // コンパイルエラーが発生しない

以下の記事に詳しく載っていましたので、ここは割愛します。

https://qiita.com/saba_can00/items/696baa5337eb10c37342

4. 便利な機能(一部)

TSの組み込みの型関数などを載せます。

種類 説明
Record<Keys, Type> KeysがプロパティとなりTypeを持つレコード型を構築します
Partial<Type> 値を省略可にします
Requied<Type> 値を必須(省略不可)にします
Readonly<Type> 読み取り専用にします
constアサーション 宣言と同時に型を読み取り専用にします
Pick<Type, Keys> 指定されたKeysの型を構築します
Omit<Type, Keys> 指定されたKeysを削除して型を構築します
Exclude<Type, Union> Typeに含まれるがUnionに含まれていない型を構築します
NonNullable<Type> nullとundefinedを除外したTypeの型を構築します

詳細

Record<Keys, Type> ... KeysがプロパティとなりTypeを持つレコード型を構築します。

interface Fruit {
  color: string,
  price: number
}
type FruitName = "Apple" | "Banana"

const fruit: Record<FruitName, Fruit> = {
  Apple: { color: 'red', price: 100 },
  Banana: { color: 'yellow', price: 80 }
}

console.log(fruit.Apple) // { "color": "red", "price": 100 } 

Partial<Type> ... 値を省略可にします。Partialはundefinedを許容するもので、nullはエラーになります。

interface User {
  name: string
}
const user: Partial<User> = {
  name: undefined
}

Requied<Type> ... 値を必須(省略不可)にします。

interface User {
  name: string
}
const user: Required<User> = {
  name: undefined  // Type 'undefined' is not assignable to type 'string'.(2322)
}

Readonly<Type> ... 読み取り専用にします。

interface User {
  name: string
}
const user: Readonly<User> = {
  name: 'tanaka'
}

user.name = 'yamada' // Cannot assign to 'name' because it is a read-only property.(2540)

constアサーション ... 宣言と同時に型を読み取り専用にします。

const user = { name: 'tanaka' } as const

user.name = 'yamada' // Cannot assign to 'name' because it is a read-only property.(2540)
const user = ['tanaka'] as const

user.push('yamada') // Property 'push' does not exist on type 'readonly ["tanaka"]'.(2339)

Pick<Type, Keys> ... 指定されたKeysの型を構築します。

interface Fruit {
  color: string,
  price: number
}

type FruitColor = Pick<Fruit, 'color'>

const fruit: FruitColor = {
  color: 'red'
}

console.log(fruit.color) // "red"

Omit<Type, Keys> ... 指定されたKeysを削除して型を構築します。

interface Fruit {
  color: string,
  price: number
}

type FruitColor = Omit<Fruit, 'price'>

const fruit: FruitColor = {
  color: 'red'
}

console.log(fruit.color) // "red"

Exclude<Type, Union> ... Typeに含まれるがUnionに含まれていない型を構築します。エクスクルードと読みます。

type A = number | string
type B = number
type C = Exclude<A, B>

const a: C = 'a'
console.log(a) // "a"

NonNullable<Type> ... nullとundefinedを除外したTypeの型を構築します。そのため、宣言後にnullやundefinedを代入しようとするとエラーになります。

type A = string | null
let a: NonNullable<A> = 'a'
a = null // Type 'null' is not assignable to type 'string'.(2322)

他にも見たい場合は、こちらに載っていますのでご確認ください。
https://www.typescriptlang.org/docs/handbook/utility-types.html

以上です。

参考

https://www.oreilly.co.jp/books/9784873119045/

Discussion