LoginSignup
6
1

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-07-24

疑問

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

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
  3. You can use dark theme
What you can do with signing up
6
1