🪬

【TypeScript】?の省略と「string | undefined」のユニオン型を一緒にしない

2021/05/22に公開

省略

オブジェクト

オブジェクトのプロパティに ? を付けて宣言することで省略可能になります。

type SampleObj = {
    foo: string
    bar?: string
}

let obj: SampleObj = {
    foo: 'sample',
}

console.log(obj);
// {
//   "foo": "sample"
// }

obj = {
    foo: 'foo',
    bar: 'bar',
}

console.log(obj);
// {
//   "foo": "foo",
//   "bar": "bar"
// }

SampleObjbar? がついているので、省略可能となっています。

最初に定義した foo だけしかないオブジェクトと、 foobar を代入したオブジェクトは同じ SampleObj 型になります。

関数

関数のパラメータに ? をつけると引数が省略可能になります。

function foo(bar: number, baz?: string): void {
    console.log(`bar: ${bar}, baz: ${baz}`);
}

foo(123);           // bar: 123, baz: undefined
foo(123, 'hello');  // bar: 123, baz: hello

あるいは、パラメータ宣言の後に = 'hello' のようにしてデフォルト値の設定ができます。

関数を実行する時に、引数を指定しなかった場合にデフォルトの値が代入されます。

function foo(bar: number, baz?: string = 'hello'): void {
    console.log(`bar: ${bar}, baz: ${baz}`);
}

foo(123);           // 123, hello
foo(123, 'world');  // 123, world

undefinedをチェックする

JavaScript では存在しないプロパティにアクセスすると undefined が返却されます。

省略したプロパティや引数の型は string | undefined のように Union 型(ユニオン型)となります。
(string なのは省略しているのが string 型のため)

? で省略したプロパティや関数の引数を使用すると、 undefined の可能性があるのでエラーとなってしまいます。

そのため省略したプロパティや引数は undefined のチェックをして使用する必要があります。

type SampleObj = {
    foo: string
    bar?: string
}

function func(obj: SampleObj): void {
    // const bar = obj.bar.toUpperCase
    // Error: Object is possibly 'undefined'.
    // toUpperCaseはstringのメソッドなので、undefinedの可能性があるとエラーとなる


    // 三項演算子でundefinedかをチェックする
    const bar = obj.bar != null ? obj.bar.toUpperCase : '';

    // Null合体演算子でも可能
    // const bar = obj.bar ?? '';

    // Optional Chainingでも可能
    // const bar = obj.bar?.toUpperCase

    console.log(bar)
}

let obj: SampleObj = {
    foo: 'sample',
}

func(obj) // ""

obj = {
    foo: 'foo',
    bar: 'bar',
}

func(obj) // "BAR"

? で省略したのと「string | undefined」は同じ意味ではない

前述したように ? で省略すると string | undefined というように Union 型に変換されます。

しかし、 ? を使用せずに string | undefined と自分で定義した場合は省略できず、同じ意味にはなりません。

? を使わない場合は、たとえ undefined が許されていても、プロパティや関数の引数として宣言しないといけないのです。

type SampleObj = {
    foo: string
    bar?: string | undefined
}

let obj: SampleObj = {
    foo: 'sample',
}
// Error
// let obj: SampleObj
// Property 'bar' is missing in type '{ foo: string; }' but required in type 'SampleObj'.
// : 'bar' is declared here.

// 以下のようにundefinedと宣言するとエラーにならない
// let obj: SampleObj = {
//     foo: 'sample',
//     bar: undefined
// }


function foo(bar: number, baz: string | undefined): void {
    console.log(`bar: ${bar}, baz: ${baz}`);
}

foo(123)
// Error
// Expected 2 arguments, but got 1.(2554)
// : An argument for 'baz' was not provided.

// 以下のようにundefinedと宣言するとエラーにならない
// foo(123, undefined)

参考

https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a#-省略可能なプロパティ

https://typescript-jp.gitbook.io/deep-dive/type-system/functions

Discussion