読み込み中...

TypeScriptでスプレッド構文を活用!初心者のための10の使い方

TypeScriptのスプレッド構文のイラスト付き説明 TypeScript
この記事は約23分で読めます。

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

本記事のサンプルコードを活用して機能追加、目的を達成できるように作ってありますので、是非ご活用ください。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を満たす現役のプログラマチームによって監修されています。

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

TypeScriptはJavaScriptに静的型付けの利点を追加することで、大規模なアプリケーション開発をより安全かつ効率的にすることを目指した言語です。

その中でも、スプレッド構文は、配列やオブジェクトの要素を展開したり、マージしたりする際に非常に便利な機能として多くの開発者に利用されています。

今回の記事では、TypeScriptでのスプレッド構文の活用法を10のサンプルコードとともに紹介していきます。

初心者の方でもわかりやすく解説しているので、スプレッド構文を更に効果的に活用したいと思っている方は、ぜひ参考にしてください。

また、記事の中では注意点やカスタマイズ例も交えて詳しく説明しています。

これにより、スプレッド構文を使う上での落とし穴や、さらに応用的な使い方を知ることができます。

それでは、TypeScriptでのスプレッド構文の基本的な役割から見ていきましょう。

●TypeScriptのスプレッド構文とは

TypeScriptでのスプレッド構文は、JavaScriptのスプレッド構文を拡張したもので、複数の要素やプロパティを一つの場所に展開する際に使用されます。

具体的には、配列やオブジェクトを簡潔に展開したり、結合する際に非常に役立ちます。

この構文は「…」という三つのドットを使って表現されます。

そして、TypeScriptでは、JavaScriptの機能に加えて、型の安全性を高めるための追加的な機能も提供されています。

○スプレッド構文の基本的な役割

スプレッド構文の主な役割は次の通りです。

  1. 配列の展開:配列の中身を別の配列内で展開することができます。
  2. オブジェクトの展開:オブジェクトのプロパティを別のオブジェクト内で展開することができます。
  3. 関数の引数として使用:配列を関数の引数として展開して、それぞれの要素を個別の引数として渡すことができます。

このコードでは、配列を使ってスプレッド構文の基本的な役割を表しています。

この例では、配列の中身を新しい配列に展開しています。

// 配列の展開
let arr1 = [1, 2, 3];
let arr2 = [...arr1, 4, 5];
console.log(arr2);  // このコードは、[1, 2, 3, 4, 5] という配列を表示します。

このように、スプレッド構文は配列やオブジェクトを効率的に取り扱う際に非常に便利です。

特にTypeScriptでの型チェックの強化により、より安全にデータの操作が可能となります。

●スプレッド構文の使い方:10選

TypeScriptでスプレッド構文を活用すると、コーディングがよりシンプルで効率的になります。

初心者の方でも簡単に取り入れることができる10の具体的な方法を、サンプルコード付きで解説します。

○サンプルコード1:配列の結合

このコードでは、スプレッド構文を使って2つの配列を結合する方法を表しています。

この例では、配列arr1arr2を結合して新しい配列combinedを作成しています。

const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];
console.log(combined);

上記のコードを実行すると、結果として[1, 2, 3, 4, 5, 6]という新しい配列が出力されます。

○サンプルコード2:オブジェクトのマージ

このコードでは、2つのオブジェクトを結合して新しいオブジェクトを作成する方法を表しています。

この例では、オブジェクトobj1obj2をマージしてmergedObjを作成しています。

const obj1 = {a: 1, b: 2};
const obj2 = {c: 3, d: 4};
const mergedObj = {...obj1, ...obj2};
console.log(mergedObj);

上記のコードを実行すると、結果として{a: 1, b: 2, c: 3, d: 4}という新しいオブジェクトが出力されます。

○サンプルコード3:関数の引数展開

このコードでは、スプレッド構文を使って配列の要素を関数の引数として展開する方法を表しています。

この例では、配列argsの要素を関数sumの引数として渡しています。

function sum(x: number, y: number, z: number): number {
    return x + y + z;
}

const args = [1, 2, 3];
console.log(sum(...args));

上記のコードを実行すると、sum関数の結果として6が出力されます。

○サンプルコード4:配列のコピー

このコードでは、スプレッド構文を使って配列のシャローコピーを作成する方法を表しています。

この例では、配列originalのシャローコピーを作成してcopiedとしています。

const original = [1, 2, 3];
const copied = [...original];
console.log(copied);

上記のコードを実行すると、[1, 2, 3]というコピーされた新しい配列が出力されます。

○サンプルコード5:配列の一部取得

TypeScriptのスプレッド構文は、配列やオブジェクトの内容を簡単に他の場所に展開するのに非常に便利です。

特に、配列の一部だけを新しい配列として取り出したい場合に、スプレッド構文を活用すると、コードが読みやすくなります。

このコードでは配列の一部を新しい配列として取得する方法を表しています。

この例では、配列の最初と最後の要素を除外して新しい配列を作成しています。

const numbers = [1, 2, 3, 4, 5];
const [first, ...middle] = numbers;
const last = middle.pop();

console.log(middle);  // コメント:middleは[2, 3, 4]となります

上記のコードを実行すると、middleという新しい配列には[2, 3, 4]が格納されます。

first変数には元の配列の最初の要素1が、last変数には最後の要素5がそれぞれ格納されます。

この方法で、配列の任意の部分を取得することができます。

例えば、最初の2つの要素を除外したい場合は次のように記述することができます。

const [first, second, ...rest] = numbers;
console.log(rest);  // restは[3, 4, 5]となります。

このように、スプレッド構文を利用することで、配列の任意の範囲を効率的に取り出すことが可能です。

しかし、注意すべき点として、pop()メソッドは元の配列も変更してしまうため、元の配列を変更したくない場合は、スプレッド構文で新しい配列を作成してから処理を行うことをおすすめします。

下記の応用例では、上記の方法を応用して、配列の中央の要素のみを取得する方法を紹介します。

const getMiddleElements = (arr: any[]) => {
  const halfLength = Math.floor(arr.length / 2);
  if (arr.length % 2 === 0) {
    return [arr[halfLength - 1], arr[halfLength]];
  } else {
    return [arr[halfLength]];
  }
}

const result = getMiddleElements(numbers);
console.log(result);  // numbersが[1, 2, 3, 4, 5]の場合、resultは[3]となります。

この関数getMiddleElementsは、引数として配列を受け取り、その配列の中央の要素を返します。

配列の要素数が偶数の場合は中央の2つの要素、奇数の場合は中央の1つの要素を返すようにしています。

○サンプルコード6:ネストされたオブジェクトのマージ

TypeScriptでのプログラム開発中に、深い階層を持つオブジェクトをマージする必要がある場合、スプレッド構文を利用することで、この作業をシンプルに、そして簡潔に実行できます。

ここでは、ネストされたオブジェクトのマージを効果的に行う方法を、サンプルコードをもとに詳しく紹介していきます。

// 2つのネストされたオブジェクトを用意
const objA = {
    id: 1,
    user: {
        name: "山田",
        address: {
            city: "東京",
            postcode: "100-0001"
        }
    }
};

const objB = {
    user: {
        name: "佐藤",
        address: {
            postcode: "540-0001"
        }
    }
};

// スプレッド構文を用いてネストされたオブジェクトをマージ
const mergedObj = {
    ...objA,
    user: {
        ...objA.user,
        ...objB.user,
        address: {
            ...objA.user.address,
            ...objB.user.address
        }
    }
};

console.log(mergedObj);

このコードでは、objAobjBという2つのネストされたオブジェクトをマージしています。

最も外側のオブジェクトのマージはシンプルですが、内部のuserオブジェクトやさらに深くaddressオブジェクトのマージには、再度スプレッド構文を用いて指定する必要があります。

上記のサンプルコードを実行すると、objAobjBの情報が統合された新しいオブジェクトmergedObjが生成されます。

ここで、objBの情報がobjAの情報を上書きしてマージされています。

実行すると、得られるオブジェクトの内容は次の通りです。

{
  "id": 1,
  "user": {
    "name": "佐藤",
    "address": {
      "city": "東京",
      "postcode": "540-0001"
    }
  }
}

ネストされたオブジェクトのマージは一見複雑に見えるかもしれませんが、スプレッド構文をうまく活用することで、このように明瞭なコードで実現できます。

しかしながら、深い階層を持つオブジェクトのマージを行う際には、注意が必要です。

ネストの深さが変わると、マージの方法も変わるため、正確なマージの方法を理解しておくことが大切です。

また、特定のプロパティだけを取り除いてマージしたい場合、分割代入を組み合わせることで実現できます。

const { user: { address, ...restUser }, ...restObjA } = objA;
const mergedWithoutAddress = { ...restObjA, user: { ...restUser } };
console.log(mergedWithoutAddress);

このコードでは、objAからaddressプロパティを取り除いた新しいオブジェクトmergedWithoutAddressを生成しています。

このように、スプレッド構文と分割代入を組み合わせることで、柔軟にオブジェクトの操作が可能になります。

○サンプルコード7:条件付きオブジェクト展開

TypeScriptでは、スプレッド構文を利用して、特定の条件を満たす場合にだけプロパティをマージするというような、条件付きのオブジェクト展開が可能です。

この方法を活用することで、柔軟なオブジェクトの生成や更新が行えるようになります。

このコードでは、ある条件が真のときだけ特定のプロパティをオブジェクトに追加する方法を表しています。

この例では、isAvailableという変数が真の場合にのみ、availability: 'available' というプロパティをオブジェクトに追加しています。

const isAvailable = true; // これをfalseに変更して挙動の違いを確認してみてください

const product = {
  id: 1,
  name: '商品A',
  ...(isAvailable ? { availability: 'available' } : {})
};

console.log(product);

このサンプルコードを実行すると、isAvailableが真の場合にはavailability: 'available'というプロパティがproductオブジェクトに追加されます。

一方、isAvailableが偽の場合には、そのプロパティは追加されません。

このように、条件によってオブジェクトのプロパティを動的に制御することができます。

実際に上記のコードを試してみると、productオブジェクトは次のように出力されるでしょう。

{ id: 1, name: '商品A', availability: 'available' }

この技術は、APIからのレスポンスなど、受け取るデータによって動的にオブジェクトを生成する際に非常に役立ちます。

たとえば、APIのレスポンスに特定のフィールドが存在する場合だけ、そのフィールドをローカルのオブジェクトに追加したいという場合などに使用することができます。

○サンプルコード8:配列の挿入

TypeScriptでのスプレッド構文は、配列やオブジェクトの要素を展開して使うことができます。

特に配列に新しい要素を挿入する際に、このスプレッド構文は非常に役立ちます。

このコードでは配列に新しい要素を挿入する方法を紹介しています。

具体的には、指定した位置に新しい要素を追加して、新しい配列を作成することができます。

下記の例では、配列oldArrayの2番目の位置に"新しい要素"を追加し、newArrayとして新しい配列を生成しています。

const oldArray = ["要素1", "要素2", "要素3"];
// 2番目の位置に"新しい要素"を追加
const newArray = [...oldArray.slice(0, 2), "新しい要素", ...oldArray.slice(2)];
console.log(newArray); // ["要素1", "要素2", "新しい要素", "要素3"]

この方法で、既存の配列を変更することなく、新しい要素を任意の位置に追加した新しい配列を作成することができます。

特に、sliceメソッドとスプレッド構文を組み合わせることで、非常に簡潔に配列の要素の挿入や削除が可能です。

この例を実際に実行すると、結果は["要素1", "要素2", "新しい要素", "要素3"]という新しい配列が生成されます。

このようにして、簡単に配列の中に新しい要素を挿入することができるのです。

しかし、この方法にも注意が必要です。

sliceメソッドは新しい配列を返すため、大量のデータを扱っている場合や頻繁に要素の追加・削除を行う場合には、パフォーマンスの低下が考えられます。

そのため、使用する際には適切な方法を選ぶようにしましょう。

さらに、この方法は配列の要素の挿入に特化していますが、配列の要素の削除にも応用することができます。

例えば、次のようにして、oldArrayから2番目の要素を削除することができます。

const arrayWithoutSecondElement = [...oldArray.slice(0, 2), ...oldArray.slice(3)];
console.log(arrayWithoutSecondElement); // ["要素1", "要素3"]

この例を実際に実行すると、結果は["要素1", "要素3"]という新しい配列が生成されます。

このようにして、簡単に配列の中から特定の要素を削除することができます。

○サンプルコード9:オブジェクトのディープコピー

TypeScriptにおけるスプレッド構文は、オブジェクトのディープコピーを作成する際にも活用されます。

しかし、スプレッド構文だけではネストされたオブジェクトのディープコピーは実現できません。

ここでは、スプレッド構文と組み合わせてディープコピーを実現する方法を紹介します。

// このコードではJSONを使ってネストされたオブジェクトのディープコピーを作成する例を表しています。
const original = {
    name: "Taro",
    age: 30,
    address: {
        city: "Tokyo",
        country: "Japan"
    }
};

const copied = JSON.parse(JSON.stringify(original));

// ディープコピーの確認
copied.address.city = "Osaka";
console.log(original.address.city);  // "Tokyo"
console.log(copied.address.city);    // "Osaka"

この例では、オリジナルのオブジェクトとコピーのオブジェクトがそれぞれ独立しており、一方のオブジェクトのプロパティを変更しても、もう一方には影響が出ないことを表しています。

この方法を使うことで、originalオブジェクトのaddress.cityを変更したとしても、copiedオブジェクトのaddress.cityは変更されません。

しかし、この方法はオブジェクトが関数、undefined、シンボルなどのJSONでサポートされていないデータ型を含む場合には適していません。

また、スプレッド構文とJSONメソッドを使ったディープコピーの方法は非常に簡単で便利ですが、いくつか注意点が存在します。

  1. オブジェクトが関数やundefined、シンボルを含む場合、JSONでの変換が失敗する可能性があります。
  2. 大きなオブジェクトをコピーする場合、パフォーマンスの問題が生じる可能性があります。

これらの問題を回避するための方法として、外部ライブラリを利用することも考えられます。

例えば、lodashcloneDeep関数などが、ディープコピーを効率よく行うための関数として提供されています。

// lodashを使ったディープコピーの例
import cloneDeep from 'lodash/cloneDeep';

const original = {
    name: "Taro",
    function: () => console.log("Hello"),
    symbolKey: Symbol("key")
};

const copied = cloneDeep(original);

console.log(copied.name);       // "Taro"
console.log(copied.function);  // function: () => console.log("Hello")
console.log(copied.symbolKey); // Symbol(key)

この例では、lodashcloneDeep関数を使って、関数やシンボルを含むオブジェクトのディープコピーを作成しています。

このような外部ライブラリを活用することで、より複雑なオブジェクトのディープコピーも簡単に実現できます。

○サンプルコード10:可変長の引数

スプレッド構文はTypeScriptやJavaScriptの関数の引数においても非常に便利です。

関数が受け取る引数の数が変動する場面では、このスプレッド構文が大変役立ちます。

ここでは、スプレッド構文を用いて関数に可変長の引数を渡す方法について詳しく見ていきましょう。

具体的には、次のように関数定義時の引数リストで「…args」という形式を用いることで、複数の引数を一つの配列として受け取ることができます。

// 可変長の引数を受け取る関数の定義
function sum(...numbers: number[]): number {
    // reduceメソッドを用いて、引数として与えられた全ての数値を合計する
    return numbers.reduce((prev, current) => prev + current, 0);
}

// 使用例
const result = sum(1, 2, 3, 4, 5); // 15

このコードでは、sumという関数を使って、任意の数の数値を受け取り、その合計値を計算しています。

この例では、5つの数値を引数として渡しており、その合計値として15が得られます。

このような可変長の引数の機能は、関数に対して柔軟な引数を受け取らせる場面で大変有効です。

例えば、動的に変わるデータを関数の引数として受け取りたい場合や、ユーザーの入力に応じて異なる引数を関数に渡したい場面などで役立ちます。

しかし、注意点として、可変長の引数は関数の引数リストの最後にしか指定できない点が挙げられます。

これは、関数の引数リストの途中で可変長の引数を許容すると、後続の引数の境界が不明確になるためです。

例として、次のような関数定義はTypeScriptでは許容されません。

// 不正な関数定義
// function invalidFunction(...args: string[], lastArg: number) { ... }

この関数定義では、argsが可変長の引数として定義されていますが、その後にも別の引数lastArgが定義されています。

このような関数定義は、可変長の引数の後に通常の引数が来ることを許容しないため、エラーとなります。

●スプレッド構文の注意点と対処法

TypeScriptでスプレッド構文を使う際、数多くの利点がありますが、同時に注意すべき点も存在します。

ここでは、その注意点と対処法を詳しく解説します。

○変更可能なオブジェクトへの影響

スプレッド構文は、オブジェクトや配列を新しいオブジェクトや配列に展開しますが、展開元が変更可能なオブジェクトの場合、参照が共有されることがあります。

これにより意図しない動作を引き起こす可能性が高まります。

このコードでは、userオブジェクトをupdatedUserオブジェクトにスプレッドしています。

この例では、userオブジェクトのプロパティをupdatedUserオブジェクトにコピーしています。

const user = {
    name: 'Taro',
    profile: {
        age: 20,
        city: 'Tokyo'
    }
};

const updatedUser = {
    ...user,
    profile: {
        ...user.profile,
        city: 'Osaka'
    }
};

console.log(updatedUser); // { name: 'Taro', profile: { age: 20, city: 'Osaka' } }

しかし、updatedUserprofileプロパティのみを変更したつもりでも、userオブジェクトのprofileプロパティも影響を受ける可能性があります。

○ディープコピーとシャローコピー

スプレッド構文はシャローコピーを行います。

つまり、ネストされたオブジェクトの内部のオブジェクトは参照が共有されるため、コピー先のオブジェクトで変更を加えると、元のオブジェクトも変更されてしまうことがあります。

このコードでは、originalオブジェクトをcopiedオブジェクトにスプレッドしています。

この例では、originalオブジェクトのネストされたプロパティをcopiedオブジェクトにコピーしています。

const original = {
    outer: 'value',
    nested: {
        inner: 'value'
    }
};

const copied = { ...original };
copied.nested.inner = 'new value';

console.log(original.nested.inner); // "new value"

このように、ネストされたプロパティを変更したつもりが、元のオブジェクトも変更されてしまう場面が考えられます。

これを避けるためには、ディープコピーを行う必要があります。

ディープコピーを実現するには、外部ライブラリを使用する方法などが考えられますが、簡易的にはJSONメソッドを利用する方法もあります。

const deepCopied = JSON.parse(JSON.stringify(original));
deepCopied.nested.inner = 'new value for copied';

console.log(original.nested.inner); // "value"
console.log(deepCopied.nested.inner); // "new value for copied"

ただし、この方法は関数やシンボル、undefinedなどを含むオブジェクトには適用できませんので、その点を注意しながら使用してください。

●スプレッド構文のカスタマイズ方法

TypeScriptのスプレッド構文は、その基本的な使い方だけでなく、さまざまなカスタマイズや拡張も可能です。

ここでは、スプレッド構文をさらに強力に、そして柔軟に活用するための方法を2つ紹介します。

○型の絞り込みを利用した活用法

TypeScriptは静的型付け言語であるため、型を利用してオブジェクトや配列の操作をより安全に行うことができます。

スプレッド構文と組み合わせて、特定の型のみを取得・結合することも可能です。

このコードでは、特定のプロパティを持つオブジェクトのみを取得するための型を定義し、スプレッド構文を使用してオブジェクトをマージしています。

この例では、nameageプロパティを持つオブジェクトのみを取得し、それを新しいオブジェクトとして結合しています。

type Person = {
    name: string;
    age: number;
};

const person1: Person = { name: "太郎", age: 25 };
const person2: Person = { name: "花子", age: 30 };

const mergedPerson: Person = { ...person1, ...person2 };
console.log(mergedPerson); // { name: "花子", age: 30 }

上記のコードを実行すると、mergedPersonオブジェクトはperson2のプロパティで上書きされた結果が得られます。

○カスタム関数を組み合わせる方法

スプレッド構文だけでなく、カスタム関数を組み合わせることで、より複雑な操作を行うこともできます。

このコードでは、2つのオブジェクトを受け取り、それぞれのプロパティの値を加算するカスタム関数を定義しています。

この例では、value1value2プロパティの値を加算し、新しいオブジェクトとして返しています。

type SampleObject = {
    value1: number;
    value2: number;
};

const addObjects = (obj1: SampleObject, obj2: SampleObject): SampleObject => {
    return {
        value1: obj1.value1 + obj2.value1,
        value2: obj1.value2 + obj2.value2
    };
}

const object1: SampleObject = { value1: 10, value2: 20 };
const object2: SampleObject = { value1: 5, value2: 15 };

const resultObject = addObjects(object1, object2);
console.log(resultObject); // { value1: 15, value2: 35 }

上記のコードを実行すると、resultObjectvalue1が15、value2が35という結果が得られます。

まとめ

TypeScriptのスプレッド構文は非常に便利で、配列やオブジェクトの操作を簡単に、そしてエレガントに行うことができます。

スプレッド構文を使うことで、複雑なオブジェクトや配列の操作も、一見して何をしているかが理解しやすくなります。

特にTypeScriptの静的型付けの特性を活かして、型の絞り込みやカスタム関数と組み合わせることで、より堅牢なコードを書くことができます。

これからもTypeScriptを楽しみながら、より質の高いコードを書き続けるために、スプレッド構文をはじめとする便利な機能をフルに活用してください。