LoginSignup
1

More than 3 years have passed since last update.

posted at

updated at

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

疑問

オプショナル型の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は同じではない
  • オブジェクトでの実装時には、プロパティを省略できるかできないかが違う
  • クラスでの実装時には、どちらの型定義でもプロパティへの代入を省略できてしまう

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1