【TypeScript】型について(基礎〜ちょっと応用)

ゆーたろー

こんにちは、ゆーたろーです。

HRTechベンチャーのエンジニアです。
TypeScript/Vue.js(Nuxt.js)/React/Next.js/PHP/LaravelでWebアプリケーションの開発を行っています。

・副業でSassスタートアップで開発(TS/React/Next.js)
・神戸で勉強会「つながる勉強会」を運営
・神戸メインのグルメインスタ運営

など色々やっています。1児のパパです。

TypeScriptの型について自分が勉強したことをまとめます。

TypeScriptの型については既に体系的にまとまっている記事があるのでこの記事は「車輪の再開発」になってしまっているのですが自分の勉強用として調べたことなどをまとめるために敢えて書きます。この記事を書くのに参考にした記事は最後にまとめておきます。

目次

TypeScriptの概要

ここはおまけです。
(十分に調べられてもいないので必要に応じて飛ばしてください)

TypeScriptの型(基本)

TypeScriptを使う上でこれは必ず理解しておきたいという基本的な型についてまとめます。

string

文字列しか受け付けなくなる。

const name: string = '太郎' // OK
const name: string = 100 // NG

number

数字しか受け付けなくなる。

const age: number = 30 // OK
const age: number = '30' // NG

boolean

真偽値(true or false)しか受け付けなくなる。

const isAdmin: boolean = true // OK
const isAdmin: boolean = 'true' // NG

もちろん0 = false1 = trueという判定もしてくれないです。

const isAdmin: boolean = 0 // NG
const isAdmin: boolean = 1 // NG

null

nullしか受け付けなくなる。

const nullConst: null = null // OK
const nullConst: null = 'null' // NG

undefined

undefinedしか受け付けなくなる。

const undefinedConst: undefined = undefined // OK
const undefinedConst: undefined = 'undefined' // NG

any

どんな型のデータも受け付ける。

// 全部OK
const any1: any = '文字列'
const any2: any = 100
const any3: any = false
const any4: any = null
const any5: any = ['リンゴ', 'バナナ', 'オレンジ']

any型はどんな型に受け付けるので、TypeScriptを使う意味がないです。

以下のようなケースで使うことが望ましい。

  • JavaScript→TypeScriptのリプレイスの初期段階
    • まずは暫定でany型で型定義してリプレイスを進めていく中で少しずつ減らす
    • 最終的には0にする
  • 型がまだ確定できない時
    • 必ずコメントを残して最終的に残らないようにする

配列

配列型の定義の仕方は2つあります。

// 1. Array<type>
// ジェネリクスという機能を使う
const array1: Array<number> = [1, 2, 3, 4, 5]

// type[]
let array2: number[] = [1, 2, 3, 4, 5]

上記のようにArray<number>もしくはnumber[]とすることで「数字の配列」型になるので、文字列の要素を追加したり配列以外の型のデータを格納しようとするとエラーになります。

const array1: Array<number> = [1, 2, 3, 4, 5]

// NG
array1.push('string')
let array1 = 100 

オブジェクト

オブジェクト型は各プロパティー(キー+バリュー)の型(キーの名前、バリューのデータ型)を定義できます。

// nameには文字列、ageには数字が入るオブジェクト
const obj: { name: string, age: number } = {
  name: '太郎',
  age: 30
}

objが持てるキーはnameageだけなので型定義にないキーのプロパティを持つことはできないです。

// エラー
const obj: { name: string, age: number } = {
  name: '太郎',
  age: 30,
  isAdult: true
}

逆も然りです。

// エラー
// isAdultを持たないといけないのにobjには定義されていない
const obj: { name: string, age: number, isAdult: boolean } = {
  name: '太郎',
  age: 30,
}

オブジェクト型は型定義の部分は長くなってしまうことが多々あるのでtypeを使って型定義を切り出すのも有効。

// これまでのようにそのまま定義すると見づらい...
const obj1: {
  name: string,
  age: number,
  isAdult: boolean,
  country: string,
  hobbies: Array<string>
} = {
  name: '太郎',
  age: 30,
  isAdult: true,
  country: Japan,
  hobbies: ['筋トレ', '料理', '買い物']
}

// typeを使って型定義を切り出す

// typeを定義
type Obj = {
  name: string,
  age: number,
  isAdult: boolean,
  country: string,
  hobbies: Array<string>
}

const obj2: Obj = {
  name: '太郎',
  age: 30,
  isAdult: true,
  country: Japan,
  hobbies: ['筋トレ', '料理', '買い物']
}

個人的には後者のtypeを使った定義を仕方をよく使います。

関数

関数の型定義は引数返り値それぞれでできます。

型定義を省略した場合は以下のような扱いになります。

  • 引数:any型として扱われる
  • 返り値:コンパイラが型推論する

引数の型定義に関してはTypeScriptの設定内容を定義するtsconfig.jsonnoImplicitAnytrueに設定することで指定を必須にする(any型を許容できなくする)ことができます。

関数の型定義の方法は以下のとおり。

// 引数に数字、返り値に数字の型定義
// アロー関数
const func1 = (num: number): number => num + 1

// アロー関数を使わない
const func1 = function (num: number): number {
  return num + 1
}

// 何も返さない時はvoid
const func2 = (num: number): void => console.log(num + 1)

上記で定義したfunc1自体の方は以下となります。

const func1: (num: number) => number

その他

他にも

  • symbol型
  • bigint型
  • never型

などの型がありますが、僕は使ったことがないので省略。
(never型については後ほど少しだけ触れます)

TypeScriptの型(ちょっと応用)

ここまでにまとめた基本的な型が理解できた後に理解したい型をまとめます。

(ただいきなりTypeScript未経験の状態でTypeScriptを使っているPJに入った場合は同時並行で理解していく必要があります。ちなみにそれ僕です。)

intersection型(交差型)

2つの型を合わせてできた型のことです。

以下のコードのように&を使うことで新しい方を作ります。

// 直で書く
const obj: { name: string } & { age: number } & { isAdult: boolean } = {
  name: '太郎',
  age: 30,
  isAdult: true
}

// これと同じになる
const obj: { name: string, age: number, isAdult: boolean } = {
  name: '太郎',
  age: 30,
  isAdult: true
}

まあ正直上記のように1つの1つを合体させるのは効果的ではないので、実際にはこんな感じでtypeを混ぜて使うことが多いと思います。

type Type1 = {
  name: string
  age: number
}

type Type2 = {
  name: string
  isAdult: boolean
}

// &で新しい型Type3を作る
type Type3 = Type1 & Type2

const obj1: Type3 = {
  name: '太郎',
  age: 30,
  isAdult: true
}

// これでもOK
const obj2: Type1 & Type2 = {
  name: '太郎',
  age: 30,
  isAdult: true
}

Type1とType2でname: stringが共通しているのでマージされます。(=1つとしてカウントされる)

intersection型を使う時の注意点は実際にはあまり発生しないかもですが、キーが同じでバリューの型が違うオブジェクトの型を合体させる時かなと思います。

type Type1 = {
  name: string
  age: number
}

type Type2 = {
  name: number // Type1ではstring型
  isAdult: boolean
}

// &で新しい型Type3を作る
type Type3 = Type1 & Type2

const obj: Type3 = {
  name: '太郎', // Type 'string' is not assignable to type 'never'. となりエラー
  age: 30,
  isAdult: true
}

片方がname: string、片方がname: numberとなっており、これを合体するとname: neverになってしまいます。

never型は値を持たない型なので、ここで新しく作ったType3の型定義をしたオブジェクトのnameプロパティにどんな値を入れてもエラーになります。つまり使うケースがない型です。

union型(合併・共用体)

複数の型を許容する型です。

よくある「AまたはB」で、|を使います。

// val1は文字列、数字どちらでも許容される
const val1: string | number = '太郎' //OK
const val2: string | number = 1000 //OK
const val3: string | number = false // NG

// undefinedやnullと一緒に使うことが多い(気がする)
const val4: string | null = '太郎' 

// typeを使うこともできる
type Type1 = string | number
// |を使うことで string | number | null の型として扱える
const val5: Type1 | null = 1000

utility型(ユーティリティ型)

ユーティリティ型は直感的には理解しにくい印象ですが、TypeScriptの組み込み機能であり以下の記事では「コード内で型変換を容易にする為にTypeScriptが提供する(便利な関数のような)型達」と書かれています。

上記記事を見るとわかりますが、ユーティリティ型は結構な量があるので全部は書きませんがオブジェクトの型に関係するものを3つピックアップします。

Partial<T>

Tに指定するオブジェクト型の全てのプロパティを省略可能な型を生成するものです。
(ここもジェネリクスを使っているのでジェネリクスは別の記事にまとめます)

// 型定義
type User = {
  name: string
  age: number
  isAdult: boolean
}

// age, isAdultのプロパティがないけど問題ない
const obj: Partial<User> = {
  name: '太郎'
}

// Partial<User>はこのような型になる
type Partial<User> = {
  name?: string
  age?: number
  isAdult?: boolean
}

// キー名?で省略可能となりこれと同じ意味を持つ
type Partial<User> = {
  name: string | undefined
  age: number | undefined
  isAdult: boolean | undefined
}

objはキーがnameのプロパティしか持っていないですが、エラーにはなりません。

Readonly<T>

Tに指定するオブジェクト型の全てのプロパティを読み取り専用(上書き不可)な型を生成するものです。

// 型定義
type User = {
  name: string
  age: number
  isAdult: boolean
}

const obj: Readonly<User> = {
  name: '太郎',
  age: 30,
  isAdult: true
}

// 読み取り専用なので上書きできない
obj.age = 35 // Cannot assign to 'age' because it is a read-only property.

// Readonly<User>はこのような型になる
type Partial<User> = {
  readonly name: string
  readonly age: number
  readonly isAdult: boolean
}

readonlyプロパティをつけることで上書き(更新)できなくなります。

Required<T>

Tに指定するオブジェクト型の全てのプロパティを必須な型を生成するものです。

// 型定義
type User = {
  name: string
  age: number
  isAdult: boolean
}

// age, isAdultのプロパティがないけど問題ない
const obj: Required<User> = {
  name: '太郎',
  age: 30,
  isAdult: true
}

// isAdultは必須なのでエラーになる
// Property 'isAdult' is missing in type '{ name: string; age: number; }' but required in type 'Required<User>'.
const obj2: Required<User> = {
  name: '太郎',
  age: 30
}

明示的に必須という条件をつけることができます。

最後に

参考記事

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

この記事を書いた人

Webエンジニア←KHI(プラント事業の技術職)←大学院(機械工学)

PHP/Laravel/TypeScript/React/Next.js(Vue.js/Nuxt.jsは少し)
バックエンド歴の方が長いです。

神戸で「つながる勉強会」を運営↓
https://tsunagaru-kobe.connpass.com/

コメント

コメント一覧 (1件)

コメントする

目次
閉じる