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

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

省略

オブジェクト

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

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)

参考