Help us understand the problem. What is going on with this article?

オプショナル型とundefinedとのユニオン型は、オブジェクトとクラスで挙動が違った

More than 1 year has passed since last update.

疑問

オプショナル型のhoge?: stringと、ユニオン型hoge: string | undefinedがあったとき、両者の挙動に違いがあるのかないのかを検証したくなりました。

なおstrictNullCheckstrictPropertyInitializationが有効な状況を前提とします。

検証

ブラウザ(Chrome)上で、次のコードを使って検証しました:TypeScirpt 公式 Playground
(TypeScriptのバージョンや細かいオプションなどはリンク先の設定を参照)

先に検証結果

  • オブジェクトで実装した場合
    • オプショナル型hoge?: stringではhogeプロパティを省略できる
    • ユニオン型hoge: string | undefinedのときはhogeはプロパティを省略できない
  • クラスで実装した場合
    • どちらの型定義でもhogeプロパティへの代入を省略できる
  • hoge?: stringhoge: string | undefinedは違う型として評価される

オブジェクトの場合の挙動

まず2つのインタフェースを宣言します。

interface MyInterface1 {
  hoge?: string;
}

interface MyInterface2 {
  hoge: string | undefined;
}

MyInterface1の実装ではオプショナルなプロパティを省略できますが、
MyInterface2の実装ではプロパティは省略できません。

const obj1: MyInterface1 = {
  // オプショナルなので省略できる
}
const obj2: MyInterface2 = {
  // オプショナルでないプロパティは省略できない。省略するとコンパイルエラー
  hoge: undefined
}

結果どちらの実装でもhogeプロパティはundefinedにできますが、
hasOwnPropertyをしたときには違いが出ます。

console.log(obj1.hasOwnProperty('hoge')); // false
console.log(obj2.hasOwnProperty('hoge')); // true

MyInterface2はMyInterface1を包含する型として評価されます。
そのため、MyInterface1の型にMyInterface2の型を代入することは可能です。

// これは可
const obj3: MyInterface1 = obj2;

その反対はできません。

// これはコンパイルエラー
const obj4: MyInterface2 = obj1;

クラスの場合の挙動

インタフェースの例と同じ型定義で2種類のクラスを宣言します。
このとき、コンストラクタ内でプロパティへの代入がなくてもエラーは出ません。

class MyClass1 {
  public hoge?: string;

  constructor() {
    // オプショナルなので省略できる
  }
}

class MyClass2 {
  public hoge: string | undefined;

  constructor() {
    // 代入しなくてもコンパイルが通る!
  }
}

どちらのクラスのインスタンスでもhogeについてのhasOwnPropertyの結果はfalseになります。

const myClass1 = new MyClass1();
const myClass2 = new MyClass2();

console.log(myClass1.hasOwnProperty('hoge')); // false
console.log(myClass2.hasOwnProperty('hoge')); // false

MyClass2はMyClass1の型を包含するのでMyClass1の型にMyClass2の型を代入することは可能です。
その逆の代入はできません。

// これは可
const myClass3: MyClass1 = new MyClass2();
// これはコンパイルエラー
const myClass4: MyClass2 = new MyClass1();

インタフェースの型とクラスの型の互換性

同じ型定義なので、当然相互の代入が可能です。

const myObj1: MyInterface1 = myClass1;
const myObj2: MyInterface2 = myClass2;
const myObj3: MyClass1 = obj1;
const myObj4: MyClass2 = obj2;

まとめ

確認した挙動をまとめるとこのようになります。

  • オプショナル型のhoge?: stringと、ユニオン型hoge: string | undefinedは同じではない
  • オブジェクトでの実装時には、プロパティを省略できるかできないかが違う
  • クラスでの実装時には、どちらの型定義でもプロパティへの代入を省略できてしまう
y_hokkey
1988年生まれ。多摩美グラフィックデザイン学科を中退。デザイナーとしてグラフィック・エディトリアル・Web・UIのデザインを経験した後、Webフロントエンドエンジニアも経験。現在はITコンサルタントとして外資系SIerに在籍中。元LIG社員。※投稿内容は私個人の意見であり、所属企業・部門見解を代表するものではありません。
http://media-massage.net/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした