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

◆ Live配信スケジュール ◆
サイオステクノロジーでは、Microsoft MVPの武井による「わかりみの深いシリーズ」など、定期的なLive配信を行っています。
⇒ 詳細スケジュールはこちらから
⇒ 見逃してしまった方はYoutubeチャンネルをご覧ください
【5/21開催】Azure OpenAI ServiceによるRAG実装ガイドを公開しました
生成AIを活用したユースケースで最も一番熱いと言われているRAGの実装ガイドを公開しました。そのガイドの紹介をおこなうイベントです!!
https://tech-lab.connpass.com/event/315703/

はじめに

こんにちは、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のあれこれ①型ガードについて

 

アバター画像
About 細川 13 Articles
高専専攻科制御系の学科を卒業後、2022年にサイオステクノロジーに入社。入社後は、バックエンド, フロントエンドの開発に携わり、Go言語やTypeScriptを用いた開発を行っている。その他、React, Next.JS, Nest.JS, GraphQLなど
ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

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

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


ご覧いただきありがとうございます。
ブログの最新情報はSNSでも発信しております。
ぜひTwitterのフォロー&Facebookページにいいねをお願い致します!



>> 雑誌等の執筆依頼を受付しております。
   ご希望の方はお気軽にお問い合わせください!

Be the first to comment

Leave a Reply

Your email address will not be published.


*


質問はこちら 閉じる