TypeScriptのあれこれ②違う型なのに代入できる!?

はじめに

こんにちは、PSの新卒2年目エンジニアの細川です。普段の業務ではTypeScriptやGoを使った開発を行っています。

今回はTypeScriptの部分型についてまとめてみようと思います。

TypeScriptを書き始めたばかりのころに、「型が違うのにエラーにならない!?」って思ったことがある方はいませんか?

本ブログではこのような事態の原因になりうるTypeScriptの部分型についてまとめてみたいと思います。間違っているところありましたら、優しく教えていただければと思います。

TypeScriptとは

TypeScriptはJavaScriptの文法を拡張したものです。主に型に関する部分が拡張されています。TypeScriptはコンパイルすることで、JavaScriptになります。

イメージ的には、JavaScriptに型をつけたイメージです。

より詳細な定義が気になる方はこちらを読んでみてください。

TypeScriptでの型付け

TypeScriptで採用されている型の定義は構造的部分型と呼ばれるものです。

型の定義の仕方は公称型と構造的部分型とがあり、構造的部分型は比較的緩めの型付けとなっています。

構造的部分型では、必要なプロパティを持っていれば、違う型でもエラーになりません。実際にコードを見てみましょう。

代入の場合

まず、例として以下のようなコードを書いてみます。

 

type TypeA = {
  a: string;
};
type TypeAandB = {
  a: string;
  b: string;
};

const ab: TypeAandB = {
  a: "hoge",
  b: "hoge",
}
const a: TypeA = ab
console.log(a);

// 出力結果:{ a: 'hoge', b: 'hoge' }

a.b // エラー

aはTypeA型として宣言していますが、TypeAandB型を代入してもエラーになりません。

Aを出力してみると、きちんとaとbの両方が保持されています。ただし、bのプロパティにはアクセスできなくなります。

関数の引数の場合

他にも以下のようなコードを考えてみます。

const change_a = (obj: TypeA, s: string): TypeA => {
  const result: TypeA = {...obj, a: s}
  return result
}
const ab: TypeAandB = {
    a: "wa-",
    b: "wa-",
}
const result: TypeA = change_a(ab, "hoge")
console.log(result)
// 出力結果:{ a: 'hoge', b: 'wa-' }

このコードでは、change_aという関数は引数にTypeA型を受け取るように宣言していますが、TypeAandB型を渡してもエラーにならず、そのまま処理を行っています。

これはTypeA型がTypeAandB型の部分型だからです。

TypeScriptで採用している構造的部分型ではその型に必要なプロパティがあるかどうかしかチェックしていません。今回の場合ではstring型のaというプロパティを持つオブジェクトであれば、なんでもOKになります。

これを知ってないと、「関数の引数に違う型を渡したのにエラーにならない!」とちょっとだけびっくりしたりします。

部分型?

また、部分型と聞くと、部分じゃないといけないように感じますが、以下のような場合でもエラーを出さずに実行することができます。

type Human = {
	name: string;
	age?: number;
};

type Company = {
	name: string;
	num_of_employees?: number;
	address?: string;
};

const change_name = (company: Company, name: string): Company => {
	const result: Company = {...company, name: name}
	return result
};

const h1: Human = {
	name: "Taro",
	age: 15,
};

const result: Human = change_name(h1, "Hanako");
console.log(result);

↑の場合、companyhumanの部分型となっています。プロパティの数はcompanyのほうが多いのですが、必須のプロパティが一緒なので、companyhumanの部分型になることができます。つまり、TypeScriptは必須のプロパティが足りてるかどうかしかみてなさそうですね。

 

終わりに

このように、直感ではエラーになりそうでも、エラーにならないことが意外とあるので、業務で実装を行うときには頭の片隅にこの知識を置いておくといいかもしれません。

迷ったら、「TypeScriptは、必須のプロパティが足りてるかのみを確認している!」と覚えておくといいと思います。

TypeScript以外にもGo言語などもこの構造的部分型を採用しているそうなので、使う際は注意してみてください。

また、このように緩めの型付けを採用しているTypeScriptですが、より厳密に型付けを行う方法をこちらのブログで紹介していますので、よろしければ参考にしてください。

 

以前の投稿はこちら⇒TypeScriptのあれこれ①型ガードについて

 

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です