TypeScriptで配列を結合しようとして、「型エラーが出てしまう」「気づいたら型がany[]になっている」「readonly配列が結合できない」といった壁にぶつかっていませんか?JavaScriptでは問題なく動いていたconcat()やスプレッド構文も、TypeScriptでは型推論の仕組みを理解していないと、思わぬエラーや型の崩壊を招いてしまいます。
特に実務では、異なる型の配列を結合したり、オブジェクト配列のID重複を排除したり、nullやundefinedが混入したデータを安全に処理する必要があります。「とりあえず動けばいい」ではなく、型安全で保守性の高いコードを書くことが求められる現場では、正しい配列結合のテクニックが不可欠です。
この記事では、TypeScriptにおける配列結合の基本から実務レベルの応用テクニックまで、コピペで使える実装例とともに徹底解説します。
型エラーに悩まされることなく、自信を持って配列操作ができるようになりましょう。
TypeScriptで配列を結合する基本|concatとスプレッド構文の違い
TypeScriptで配列を結合する方法は大きく分けて2つあります。従来のconcat()メソッドと、モダンなスプレッド構文([...a, ...b])です。どちらも配列を結合できますが、型推論の挙動やパフォーマンス特性が異なるため、状況に応じた使い分けが重要です。
Array.concat()の基本構文と戻り値の型推論
concat()は配列のメソッドとして古くから存在する、最も基本的な結合方法です。
// 基本的な使い方
const numbers1: number[] = [1, 2, 3];
const numbers2: number[] = [4, 5, 6];
const combined: number[] = numbers1.concat(numbers2);
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 型推論が正しく動作する例
const fruits = ["apple", "banana"];
const vegetables = ["carrot", "potato"];
const foods = fruits.concat(vegetables); // 型: string[]
concat()の重要なポイントは、元の配列を変更せず、新しい配列を返すことです。TypeScriptでは元の配列の型が明確であれば、戻り値の型も正しく推論されます。
// 複数の配列を一度に結合することも可能
const array1 = [1, 2];
const array2 = [3, 4];
const array3 = [5, 6];
const result = array1.concat(array2, array3); // 型: number[]
console.log(result); // [1, 2, 3, 4, 5, 6]
ただし、配列以外の値を渡した場合の挙動に注意が必要です。
const base = [1, 2, 3];
// 配列ではなく単一の値を渡すと、その値が要素として追加される
const withSingle = base.concat(4, 5); // 型: number[]
console.log(withSingle); // [1, 2, 3, 4, 5]
// ネストした配列は1階層のみフラット化される
const withNested = base.concat([[6, 7]]); // 型: (number | number[])[]
console.log(withNested); // [1, 2, 3, [6, 7]]
スプレッド構文([…a, …b])によるモダンな配列結合
スプレッド構文はES2015(ES6)以降で導入された、より直感的で読みやすい配列結合の方法です。
// 基本的な使い方
const numbers1: number[] = [1, 2, 3];
const numbers2: number[] = [4, 5, 6];
const combined: number[] = [...numbers1, ...numbers2];
console.log(combined); // [1, 2, 3, 4, 5, 6]
// 型推論も正しく動作
const colors1 = ["red", "blue"];
const colors2 = ["green", "yellow"];
const allColors = [...colors1, ...colors2]; // 型: string[]
スプレッド構文の最大の利点は、視覚的に分かりやすく、柔軟性が高いことです。
// 途中に要素を挿入することも簡単
const start = [1, 2];
const end = [5, 6];
const withMiddle = [...start, 3, 4, ...end]; // [1, 2, 3, 4, 5, 6]
// 複数の配列を自然に結合
const arr1 = ["a"];
const arr2 = ["b", "c"];
const arr3 = ["d"];
const merged = [...arr1, ...arr2, ...arr3]; // 型: string[]
// 配列のコピーも簡潔に
const original = [1, 2, 3];
const copy = [...original]; // シャローコピー
スプレッド構文はconcat()と同様に新しい配列を生成するため、元の配列は変更されません。
const source = [1, 2, 3];
const extended = [...source, 4, 5];
console.log(source); // [1, 2, 3] - 元の配列は変化なし
console.log(extended); // [1, 2, 3, 4, 5]
concatとスプレッド構文はどちらを使うべきか【結論】
実務ではスプレッド構文を優先的に使用することを推奨します。ただし、状況によってはconcat()の方が適切なケースもあります。
比較表:concat vs スプレッド構文
| 項目 | concat() | スプレッド構文 […a, …b] |
|---|---|---|
| 可読性 | △ メソッドチェーンでやや冗長 | ◎ 視覚的に分かりやすい |
| 柔軟性 | △ 配列のみ結合 | ◎ 途中への要素挿入が容易 |
| 型推論 | ◎ 正確に推論される | ◎ 正確に推論される |
| パフォーマンス(小〜中規模) | ○ 十分高速 | ○ 十分高速 |
| パフォーマンス(大規模配列) | ◎ やや有利な場合がある | ○ 問題なし(数万要素レベル) |
| メソッドチェーン | ◎ そのままチェーン可能 | △ 別途配列化が必要 |
| ブラウザ対応 | ◎ 古いブラウザでも動作 | ○ ES6対応が必要(モダン環境では問題なし) |
使い分けの指針
// ✅ 推奨:通常はスプレッド構文を使用
const result1 = [...array1, ...array2, ...array3];
// ✅ メソッドチェーンで配列操作を続ける場合はconcatが便利
const result2 = array1
.concat(array2)
.filter(x => x > 0)
.map(x => x * 2);
// ✅ 大規模配列(数十万要素以上)でパフォーマンスがクリティカルな場合
// ベンチマークを取ってconcatの方が速ければconcatを選択
const hugeArray1 = new Array(100000).fill(1);
const hugeArray2 = new Array(100000).fill(2);
const result3 = hugeArray1.concat(hugeArray2);
// ✅ 途中に要素を挿入したい場合はスプレッド構文一択
const result4 = [...start, "inserted", ...end];
【結論】実務での推奨方針
- デフォルトではスプレッド構文を使う – 可読性が高く、モダンなコードベースに適している
- メソッドチェーンが続く場合は
concat()を検討 – 関数型プログラミング的な書き方と相性が良い - パフォーマンスがクリティカルな場合は実測する – 数十万要素以上の配列では計測してから判断
- TypeScriptの型推論はどちらも優秀 – 型の観点では甲乙つけがたいため、コードスタイルで選択
次のセクションでは、異なる型の配列を結合する際の型安全なテクニックを解説します。
型安全を維持する!異なる型や特殊な配列を結合するテクニック
実務では、同じ型の配列だけを結合するケースばかりではありません。string[]とnumber[]を混在させたり、readonly修飾子が付いた配列を扱ったり、nullやundefinedが混入した配列を安全に結合する必要があります。このセクションでは、TypeScriptの型システムを最大限活用して、型エラーを回避しながら配列を結合するテクニックを解説します。
string[]とnumber[]を結合してUnion型「(string | number)[]」を定義する
異なる型の配列を結合すると、TypeScriptは自動的にUnion型として推論します。
// 暗黙的な型推論に任せる方法
const strings = ["apple", "banana"];
const numbers = [1, 2, 3];
const mixed = [...strings, ...numbers];
// 型: (string | number)[] - TypeScriptが自動で推論
console.log(mixed); // ["apple", "banana", 1, 2, 3]
しかし、より複雑なケースでは明示的に型アノテーションを付けることで、意図を明確にし、予期しない型の混入を防げます。
// 明示的に型を指定する方法(推奨)
const strings: string[] = ["red", "blue"];
const numbers: number[] = [100, 200];
// Union型を明示的に宣言
const combined: (string | number)[] = [...strings, ...numbers];
// 型安全:正しい型のみ追加可能
combined.push("green"); // ✅ OK
combined.push(300); // ✅ OK
// combined.push(true); // ❌ エラー: boolean型は(string | number)[]に代入できない
型アノテーションを省略した場合の落とし穴
型推論に頼りすぎると、予期しない型が混入してもエラーにならないことがあります。
// 型推論に任せた場合
const data1 = ["text"];
const data2 = [42];
const data3 = [true]; // boolean型が混入
const allData = [...data1, ...data2, ...data3];
// 型: (string | number | boolean)[] - 意図せずbooleanも含まれる
// この時点ではエラーにならないが、後の処理で問題が起きる可能性
allData.forEach(item => {
// itemがbooleanの場合、toUpperCase()は存在しない
// if (typeof item === "string") のガードが必要に
});
明示的な型定義で安全性を担保する
// 型エイリアスで許可する型を明確に定義
type AllowedType = string | number;
const strings: string[] = ["hello", "world"];
const numbers: number[] = [1, 2, 3];
// const booleans: boolean[] = [true, false]; // これを結合しようとするとエラー
const safeData: AllowedType[] = [...strings, ...numbers];
// const unsafeData: AllowedType[] = [...strings, ...booleans];
// ❌ エラー: boolean[]はAllowedType[]に代入できない
// 関数の引数として使う場合も型安全
function processMixedArray(items: AllowedType[]): void {
items.forEach(item => {
if (typeof item === "string") {
console.log(item.toUpperCase()); // 型ガードで安全に処理
} else {
console.log(item * 2);
}
});
}
processMixedArray(safeData); // ✅ OK
ReadonlyArray(readonly型)の配列をエラーなく結合・変換する方法
readonly修飾子が付いた配列は、変更不可能な配列として扱われます。これを通常の配列と結合しようとすると、型エラーが発生します。
// readonly配列の定義
const readonlyNumbers: readonly number[] = [1, 2, 3];
const normalNumbers: number[] = [4, 5, 6];
// ❌ エラーになるパターン
// readonlyNumbers.push(4); // エラー: readonly配列は変更できない
// concat()を使った場合
const attempted1 = readonlyNumbers.concat(normalNumbers);
// 型: number[] - concatは新しい配列を返すのでOK
// スプレッド構文を使った場合
const attempted2 = [...readonlyNumbers, ...normalNumbers];
// 型: number[] - スプレッド構文も新しい配列を生成するのでOK
実は、readonly配列をスプレッド構文やconcat()で展開すると、新しい通常の配列が生成されるため、ほとんどの場合は問題なく結合できます。
// readonly配列同士の結合
const readonly1: readonly string[] = ["a", "b"];
const readonly2: readonly string[] = ["c", "d"];
const combined = [...readonly1, ...readonly2];
// 型: string[] - 新しい配列なのでreadonlyではなくなる
combined.push("e"); // ✅ OK - 通常の配列として扱える
readonly型を維持したい場合
結合後もreadonly配列として扱いたい場合は、明示的に型アノテーションを付けます。
const readonly1: readonly number[] = [1, 2, 3];
const readonly2: readonly number[] = [4, 5, 6];
// readonly型を維持して結合
const combined: readonly number[] = [...readonly1, ...readonly2];
// combined.push(7); // ❌ エラー: readonly配列には要素を追加できない
// 読み取り専用として安全に使用可能
console.log(combined[0]); // ✅ OK
console.log(combined.length); // ✅ OK
ReadonlyArray型との結合
ReadonlyArray<T>型(Utility型)を使った場合も同様に扱えます。
// ReadonlyArray型を使った定義
const config: ReadonlyArray<string> = ["setting1", "setting2"];
const defaults: string[] = ["default1", "default2"];
// 結合して新しい通常配列を生成
const allSettings: string[] = [...config, ...defaults];
// または、readonly型を維持
const readonlySettings: ReadonlyArray<string> = [...config, ...defaults];
// 実務での使用例:設定値のマージ
function mergeConfig(
base: ReadonlyArray<string>,
custom: string[]
): string[] {
return [...base, ...custom]; // 新しい配列として安全に結合
}
nullやundefinedが混入する配列を型ガード(filter)で安全に排除して結合
実務では、APIレスポンスやデータベースから取得した配列にnullやundefinedが含まれることがよくあります。これを安全に結合するには、適切な型ガードが必要です。
filter(Boolean)では型推論が不十分なケース
// APIから取得したデータ(nullが混入している想定)
const apiData1: (string | null)[] = ["apple", null, "banana"];
const apiData2: (string | null)[] = ["carrot", null, "potato"];
// ❌ filter(Boolean)では型が絞り込まれない
const attempted = [...apiData1, ...apiData2].filter(Boolean);
// 型: (string | null)[] - nullが除外されたことがTypeScriptに伝わらない
// attempted.forEach(item => {
// console.log(item.toUpperCase()); // エラー: itemはnullの可能性がある
// });
filter(Boolean)は実行時には正しくnullやundefinedを除外しますが、TypeScriptの型システムには除外したことが伝わりません。
ユーザー定義型ガード(is演算子)を使った確実な型の絞り込み
型ガード関数を定義することで、TypeScriptに「この関数を通過した値は特定の型である」と伝えることができます。
// null/undefinedを除外する型ガード関数
function isNotNullish<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
// APIから取得したデータ
const data1: (string | null)[] = ["apple", null, "banana"];
const data2: (string | undefined)[] = ["carrot", undefined, "potato"];
// 型ガードを使って安全に結合
const safeData = [...data1, ...data2].filter(isNotNullish);
// 型: string[] - null/undefinedが完全に除外された型になる
// ✅ 型安全に使用可能
safeData.forEach(item => {
console.log(item.toUpperCase()); // エラーなし!itemは確実にstring型
});
実務でよく使うパターン:複数配列のマージと型安全な処理
// 型ガード関数の定義(再利用可能)
function isNotNullish<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
// 複数のデータソースからの取得を想定
const users1: (string | null)[] = ["Alice", null, "Bob"];
const users2: (string | undefined)[] = ["Charlie", undefined];
const users3: (string | null | undefined)[] = ["David", null, undefined, "Eve"];
// すべてを結合してnull/undefinedを除外
const allUsers: string[] = [
...users1,
...users2,
...users3
].filter(isNotNullish);
console.log(allUsers); // ["Alice", "Bob", "Charlie", "David", "Eve"]
// 型安全な後続処理
const userNames = allUsers.map(name => name.toUpperCase()); // ✅ エラーなし
オブジェクト配列でのnull除外
// オブジェクト型の配列でも同じ型ガードが使える
type User = {
id: number;
name: string;
};
const userData1: (User | null)[] = [
{ id: 1, name: "Alice" },
null,
{ id: 2, name: "Bob" }
];
const userData2: (User | null)[] = [
{ id: 3, name: "Charlie" },
null
];
// 型ガードで安全に結合
const validUsers: User[] = [...userData1, ...userData2].filter(isNotNullish);
// ✅ 型安全にオブジェクトのプロパティにアクセス可能
validUsers.forEach(user => {
console.log(user.id, user.name); // エラーなし
});
複合的な型ガード:値の存在チェックと型の絞り込みを同時に行う
// より複雑な型ガード例
type Product = {
id: number;
name: string;
price?: number; // オプショナルなプロパティ
};
// priceが存在し、かつ正の値であることを保証する型ガード
function hasValidPrice(product: Product): product is Product & { price: number } {
return typeof product.price === "number" && product.price > 0;
}
const products1: Product[] = [
{ id: 1, name: "Book", price: 1500 },
{ id: 2, name: "Pen" }, // priceなし
{ id: 3, name: "Notebook", price: 0 } // 無効な価格
];
const products2: Product[] = [
{ id: 4, name: "Eraser", price: 200 },
{ id: 5, name: "Ruler" }
];
// 有効な価格を持つ商品のみを結合
const validProducts = [...products1, ...products2].filter(hasValidPrice);
// 型: (Product & { price: number })[] - priceが確実に存在する型
// ✅ 型安全に価格計算が可能
const totalPrice = validProducts.reduce((sum, product) => sum + product.price, 0);
console.log(totalPrice); // 1700 (1500 + 200)
【まとめ】型ガードを使った配列結合のベストプラクティス
// 再利用可能な型ガード関数を定義
function isNotNullish<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
// 1. 複数の配列を結合
// 2. 型ガードでnull/undefinedを除外
// 3. 型安全な配列として後続処理
const result = [
...array1,
...array2,
...array3
].filter(isNotNullish); // 型が正しく絞り込まれる
// これで安全に配列操作が可能
result.forEach(item => {
// itemは確実に非nullish型
});
次のセクションでは、オブジェクト配列の結合や重複排除など、さらに実践的なテクニックを解説します。
オブジェクト配列・連想配列・文字列結合まで:実務で使える配列結合レシピ集
実務では、単純な値の配列だけでなく、オブジェクト配列の結合やID重複の排除、多次元配列のフラット化など、より複雑な配列操作が求められます。このセクションでは、現場で頻出する配列結合のパターンを、パフォーマンスと型安全性の両面から最適化した実装例とともに解説します。
{ id: number; name: string }[] などのオブジェクト配列を結合しid重複を排除する実装例
オブジェクト配列を結合する際、最も頻出するのが「IDをキーにした重複排除」です。単純に結合するだけでは同じIDのオブジェクトが複数含まれてしまい、データの整合性が崩れます。
シンプルな結合(重複排除なし)
type User = {
id: number;
name: string;
};
const users1: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
const users2: User[] = [
{ id: 2, name: "Bob Updated" }, // id: 2が重複
{ id: 3, name: "Charlie" }
];
// 単純な結合
const combined: User[] = [...users1, ...users2];
console.log(combined);
// [
// { id: 1, name: "Alice" },
// { id: 2, name: "Bob" },
// { id: 2, name: "Bob Updated" }, // 重複している
// { id: 3, name: "Charlie" }
// ]
❌ 非推奨:filter + findを使った重複排除(計算量O(n²))
// パフォーマンスが悪い実装例
const deduped = combined.filter((user, index, self) =>
self.findIndex(u => u.id === user.id) === index
);
// 計算量: O(n²) - 配列が大きくなると極端に遅くなる
✅ 推奨:Mapを使った効率的な重複排除(計算量O(n))
Mapオブジェクトを使うことで、計算量O(n)で効率的に重複を排除できます。
type User = {
id: number;
name: string;
};
const users1: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
const users2: User[] = [
{ id: 2, name: "Bob Updated" }, // 後勝ちで上書き
{ id: 3, name: "Charlie" }
];
// Mapを使った効率的な重複排除
function mergeByKey<T extends Record<string, any>>(
arrays: T[][],
key: keyof T
): T[] {
const map = new Map<any, T>();
// すべての配列を順番に処理
for (const array of arrays) {
for (const item of array) {
map.set(item[key], item); // 同じキーがあれば上書き(後勝ち)
}
}
// Mapの値を配列に変換
return Array.from(map.values());
}
// 使用例
const mergedUsers = mergeByKey([users1, users2], "id");
console.log(mergedUsers);
// [
// { id: 1, name: "Alice" },
// { id: 2, name: "Bob Updated" }, // 後の値で上書きされている
// { id: 3, name: "Charlie" }
// ]
より実践的な実装:複数プロパティを持つオブジェクトの結合
type Product = {
id: number;
name: string;
price: number;
category?: string;
};
const products1: Product[] = [
{ id: 1, name: "Laptop", price: 1200, category: "Electronics" },
{ id: 2, name: "Mouse", price: 25 }
];
const products2: Product[] = [
{ id: 2, name: "Mouse", price: 30, category: "Electronics" }, // 価格とカテゴリが更新
{ id: 3, name: "Keyboard", price: 80, category: "Electronics" }
];
// 汎用的なマージ関数(型安全)
function mergeArraysByKey<T, K extends keyof T>(
...arrays: T[][]
): (arr: T[][], key: K) => T[] {
return (arr: T[][], key: K): T[] => {
const map = new Map<T[K], T>();
for (const array of arr) {
for (const item of array) {
map.set(item[key], item);
}
}
return Array.from(map.values());
};
}
// 使用例
const mergedProducts = (() => {
const map = new Map<number, Product>();
[...products1, ...products2].forEach(product => {
map.set(product.id, product);
});
return Array.from(map.values());
})();
console.log(mergedProducts);
// [
// { id: 1, name: "Laptop", price: 1200, category: "Electronics" },
// { id: 2, name: "Mouse", price: 30, category: "Electronics" },
// { id: 3, name: "Keyboard", price: 80, category: "Electronics" }
// ]
カスタムマージロジック:プロパティごとに上書き戦略を変える
type Item = {
id: number;
name: string;
count: number;
};
const items1: Item[] = [
{ id: 1, name: "Apple", count: 5 },
{ id: 2, name: "Banana", count: 3 }
];
const items2: Item[] = [
{ id: 1, name: "Apple", count: 2 }, // countを加算したい
{ id: 3, name: "Orange", count: 4 }
];
// カスタムマージ:countは加算、他は上書き
function mergeItems(arr1: Item[], arr2: Item[]): Item[] {
const map = new Map<number, Item>();
// 最初の配列を登録
arr1.forEach(item => {
map.set(item.id, { ...item });
});
// 2番目の配列をマージ(countは加算)
arr2.forEach(item => {
const existing = map.get(item.id);
if (existing) {
// 既存アイテムがあればcountを加算
map.set(item.id, {
...item,
count: existing.count + item.count
});
} else {
// 新規アイテムはそのまま追加
map.set(item.id, { ...item });
}
});
return Array.from(map.values());
}
const result = mergeItems(items1, items2);
console.log(result);
// [
// { id: 1, name: "Apple", count: 7 }, // 5 + 2 = 7
// { id: 2, name: "Banana", count: 3 },
// { id: 3, name: "Orange", count: 4 }
// ]
Set、filter、flatを使って重複除去・二次元配列のフラット化・大規模データの最適結合を行う
Setを使ったプリミティブ配列の重複除去
プリミティブ型(string、number、booleanなど)の配列であれば、Setを使って簡潔に重複を排除できます。
// 文字列配列の重複除去
const tags1: string[] = ["javascript", "typescript", "react"];
const tags2: string[] = ["typescript", "vue", "angular"];
const tags3: string[] = ["react", "svelte"];
// Setで重複を自動除去
const uniqueTags: string[] = [...new Set([...tags1, ...tags2, ...tags3])];
console.log(uniqueTags);
// ["javascript", "typescript", "react", "vue", "angular", "svelte"]
// 型も正しく推論される
const tagCount: number = uniqueTags.length; // ✅ 型: number
数値配列での重複除去とソート
const scores1: number[] = [85, 92, 78];
const scores2: number[] = [92, 88, 95];
const scores3: number[] = [78, 90];
// 重複除去してソート
const uniqueScores: number[] = [...new Set([...scores1, ...scores2, ...scores3])].sort((a, b) => b - a);
console.log(uniqueScores); // [95, 92, 90, 88, 85, 78]
// メソッドチェーンで簡潔に書ける
const topScores = [...new Set([...scores1, ...scores2, ...scores3])]
.sort((a, b) => b - a)
.slice(0, 3); // 上位3つ
console.log(topScores); // [95, 92, 90]
flat()を使った多次元配列のフラット化と結合
// 二次元配列のフラット化
const nested1: number[][] = [[1, 2], [3, 4]];
const nested2: number[][] = [[5, 6], [7, 8]];
// 結合してからフラット化
const flattened: number[] = [...nested1, ...nested2].flat();
console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8]
// 型も正しく推論される: number[]
より深いネスト構造のフラット化
// 三次元配列
const deepNested: number[][][] = [
[[1, 2], [3, 4]],
[[5, 6], [7, 8]]
];
// flat()の引数で深さを指定
const flatOnce: number[][] = deepNested.flat(1); // 1階層フラット化
const flatTwice: number[] = deepNested.flat(2); // 2階層フラット化
const flatAll: number[] = deepNested.flat(Infinity); // すべてフラット化
console.log(flatOnce); // [[1, 2], [3, 4], [5, 6], [7, 8]]
console.log(flatTwice); // [1, 2, 3, 4, 5, 6, 7, 8]
console.log(flatAll); // [1, 2, 3, 4, 5, 6, 7, 8]
実務パターン:オブジェクト配列のネストをフラット化
type Category = {
name: string;
items: string[];
};
const categories1: Category[] = [
{ name: "Fruits", items: ["apple", "banana"] },
{ name: "Vegetables", items: ["carrot", "potato"] }
];
const categories2: Category[] = [
{ name: "Fruits", items: ["orange", "grape"] },
{ name: "Dairy", items: ["milk", "cheese"] }
];
// すべてのitemsを1つの配列に結合
const allItems: string[] = [...categories1, ...categories2]
.flatMap(category => category.items);
console.log(allItems);
// ["apple", "banana", "carrot", "potato", "orange", "grape", "milk", "cheese"]
// 重複を除去したい場合
const uniqueItems: string[] = [...new Set(allItems)];
flatMapを使った変換と結合の同時実行
type User = {
name: string;
tags: string[];
};
const users1: User[] = [
{ name: "Alice", tags: ["developer", "typescript"] },
{ name: "Bob", tags: ["designer", "figma"] }
];
const users2: User[] = [
{ name: "Charlie", tags: ["developer", "react"] },
{ name: "David", tags: ["typescript", "nodejs"] }
];
// すべてのユーザーのタグを収集して重複排除
const allTags: string[] = [...new Set(
[...users1, ...users2].flatMap(user => user.tags)
)];
console.log(allTags);
// ["developer", "typescript", "designer", "figma", "react", "nodejs"]
大規模データの最適結合:パフォーマンス比較
// 10万件のデータを想定
const largeArray1 = Array.from({ length: 50000 }, (_, i) => i);
const largeArray2 = Array.from({ length: 50000 }, (_, i) => i + 25000); // 重複あり
// ✅ 推奨:Setを使った重複除去(高速)
console.time("Set方式");
const uniqueSet = [...new Set([...largeArray1, ...largeArray2])];
console.timeEnd("Set方式"); // 約5-10ms
// ❌ 非推奨:filterを使った重複除去(遅い)
console.time("filter方式");
const uniqueFilter = [...largeArray1, ...largeArray2].filter(
(value, index, self) => self.indexOf(value) === index
);
console.timeEnd("filter方式"); // 約500-1000ms(100倍以上遅い)
// パフォーマンス比較結果
console.log(uniqueSet.length === uniqueFilter.length); // true(結果は同じ)
メモリ効率を考慮した大規模配列の結合
// 大規模なオブジェクト配列の結合
type LargeObject = {
id: number;
data: string;
};
function mergeLargeArrays(
arrays: LargeObject[][],
keyExtractor: (item: LargeObject) => number | string
): LargeObject[] {
// Mapを使ってメモリ効率よく重複排除
const map = new Map<number | string, LargeObject>();
for (const array of arrays) {
for (const item of array) {
const key = keyExtractor(item);
// 既存の値があっても上書き(後勝ち)
map.set(key, item);
}
}
return Array.from(map.values());
}
// 使用例
const data1: LargeObject[] = Array.from({ length: 10000 }, (_, i) => ({
id: i,
data: `item-${i}`
}));
const data2: LargeObject[] = Array.from({ length: 10000 }, (_, i) => ({
id: i + 5000, // 半分重複
data: `updated-${i + 5000}`
}));
const merged = mergeLargeArrays([data1, data2], item => item.id);
console.log(merged.length); // 15000(重複排除後)
string[] を join(',') で1つの文字列にするTypeScript的ベストプラクティス
配列を文字列に結合するjoin()メソッドは、TypeScriptでも型安全に使用できます。
基本的なjoin()の使い方
// 文字列配列の結合
const fruits: string[] = ["apple", "banana", "orange"];
// カンマ区切りで結合
const csv: string = fruits.join(",");
console.log(csv); // "apple,banana,orange"
// 型も正しく推論される
const length: number = csv.length; // ✅ 型: number
// 他の区切り文字も使用可能
const spaced: string = fruits.join(" | ");
console.log(spaced); // "apple | banana | orange"
const newlined: string = fruits.join("\n");
console.log(newlined);
// apple
// banana
// orange
空配列の場合の挙動
// 空配列をjoinすると空文字列が返る
const empty: string[] = [];
const result: string = empty.join(",");
console.log(result); // ""
console.log(result.length); // 0
// 条件分岐での使用例
function formatList(items: string[]): string {
if (items.length === 0) {
return "リストが空です";
}
return items.join(", ");
}
console.log(formatList([])); // "リストが空です"
console.log(formatList(["A", "B"])); // "A, B"
number[]やboolean[]のjoin()
// 数値配列も文字列に変換される
const numbers: number[] = [1, 2, 3, 4, 5];
const numberString: string = numbers.join("-");
console.log(numberString); // "1-2-3-4-5"
// boolean配列
const flags: boolean[] = [true, false, true];
const flagString: string = flags.join(" | ");
console.log(flagString); // "true | false | true"
実務パターン:複数配列を結合してCSV形式の文字列を生成
type Row = string[];
const headers: Row = ["ID", "Name", "Email"];
const row1: Row = ["1", "Alice", "alice@example.com"];
const row2: Row = ["2", "Bob", "bob@example.com"];
// 各行をカンマ区切りにして、全体を改行で結合
const csvData: string = [headers, row1, row2]
.map(row => row.join(","))
.join("\n");
console.log(csvData);
// ID,Name,Email
// 1,Alice,alice@example.com
// 2,Bob,bob@example.com
オブジェクト配列から特定プロパティを抽出して文字列化
type User = {
id: number;
name: string;
email: string;
};
const users: User[] = [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
{ id: 3, name: "Charlie", email: "charlie@example.com" }
];
// 名前だけを抽出してカンマ区切りに
const nameList: string = users.map(user => user.name).join(", ");
console.log(nameList); // "Alice, Bob, Charlie"
// メールアドレスをセミコロン区切りに
const emailList: string = users.map(u => u.email).join("; ");
console.log(emailList); // "alice@example.com; bob@example.com; charlie@example.com"
HTMLリスト生成の例
const items: string[] = ["TypeScript", "React", "Node.js"];
// <li>タグで囲んだHTMLリストを生成
const htmlList: string = items
.map(item => `<li>${item}</li>`)
.join("\n");
const fullHtml: string = `<ul>\n${htmlList}\n</ul>`;
console.log(fullHtml);
// <ul>
// <li>TypeScript</li>
// <li>React</li>
// <li>Node.js</li>
// </ul>
join()使用時の型安全性の注意点
// ❌ 型エラーを起こす例
// const mixed: (string | number)[] = ["apple", 42, "banana"];
// const result = mixed.join(","); // これは動作するが...
// ✅ 明示的に文字列化することを推奨
const mixed: (string | number)[] = ["apple", 42, "banana"];
const safeResult: string = mixed.map(item => String(item)).join(",");
console.log(safeResult); // "apple,42,banana"
// オブジェクトが含まれる場合は特に注意
type Item = { name: string };
const objects: Item[] = [{ name: "A" }, { name: "B" }];
// ❌ これは "[object Object],[object Object]" になる
const bad: string = objects.join(",");
// ✅ プロパティを明示的に指定
const good: string = objects.map(obj => obj.name).join(",");
console.log(good); // "A,B"
実務での推奨パターン:型安全なCSVジェネレータ
type CsvRow = string[] | number[] | (string | number)[];
function toCsv(rows: CsvRow[]): string {
return rows
.map(row =>
row
.map(cell => String(cell)) // 明示的に文字列化
.join(",")
)
.join("\n");
}
// 使用例
const data: CsvRow[] = [
["Name", "Age", "City"],
["Alice", 30, "Tokyo"],
["Bob", 25, "Osaka"]
];
const csv = toCsv(data);
console.log(csv);
// Name,Age,City
// Alice,30,Tokyo
// Bob,25,Osaka
これらのテクニックを使うことで、実務で求められる複雑な配列結合処理を、型安全かつ効率的に実装できます。次のセクションでは、よくある質問とその回答を紹介します。
よくある質問(FAQ)
TypeScriptで配列を結合する際によく寄せられる質問とその回答をまとめました。実務で遭遇しやすい疑問点を中心に、具体的なコード例とともに解説します。
-
3つ以上の配列を結合する最短の書き方は?
-
スプレッド構文を使えば、配列の数に関係なく1行で結合できます。
// 3つの配列を結合 const arr1 = [1, 2]; const arr2 = [3, 4]; const arr3 = [5, 6]; const combined = [...arr1, ...arr2, ...arr3]; console.log(combined); // [1, 2, 3, 4, 5, 6] // 5つ以上でも同様 const result = [...arr1, ...arr2, ...arr3, [7, 8], [9, 10]].flat(); // または const result2 = [...arr1, ...arr2, ...arr3, ...[7, 8], ...[9, 10]];配列の配列がある場合は、
flat()やflatMap()を組み合わせます。// 配列の配列がある場合
const arrays: number[][] = [[1, 2], [3, 4], [5, 6], [7, 8]];
// 方法1: flat()を使う
const flattened = arrays.flat();
console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8]
// 方法2: reduceを使う(型安全)
const merged = arrays.reduce((acc, curr) => [...acc, ...curr], [] as number[]);
console.log(merged); // [1, 2, 3, 4, 5, 6, 7, 8]
// 方法3: concat()を使う
const concatenated = ([] as number[]).concat(...arrays);
console.log(concatenated); // [1, 2, 3, 4, 5, 6, 7, 8]
-
パフォーマンスが一番いいのは結局どれ?
-
配列のサイズと操作内容によって最適な方法が異なります。
パフォーマンス比較表
操作 小規模(〜1,000要素) 中規模(1,000〜10,000要素) 大規模(10,000要素〜) 単純な結合 スプレッド構文 ≒ concat スプレッド構文 ≒ concat concat がやや有利 重複排除(プリミティブ) Set ≫ filter Set ≫ filter Set ≫ filter(100倍以上) 重複排除(オブジェクト) Map ≫ filter Map ≫ filter Map ≫ filter(100倍以上) フラット化 flat() ≒ flatMap flat() ≒ flatMap flat() ≒ flatMap 具体的なベンチマーク例
// 小〜中規模の配列(推奨:スプレッド構文) const arr1 = Array.from({ length: 5000 }, (_, i) => i); const arr2 = Array.from({ length: 5000 }, (_, i) => i + 5000); console.time("スプレッド構文"); const spread = [...arr1, ...arr2]; console.timeEnd("スプレッド構文"); // 約0.5ms console.time("concat"); const concat = arr1.concat(arr2); console.timeEnd("concat"); // 約0.5ms // どちらも十分高速で、差はほとんどない// 重複排除が必要な場合(推奨:Set/Map) const data1 = Array.from({ length: 50000 }, (_, i) => i); const data2 = Array.from({ length: 50000 }, (_, i) => i + 25000); // 半分重複 console.time("Set使用"); const uniqueSet = [...new Set([...data1, ...data2])]; console.timeEnd("Set使用"); // 約10ms console.time("filter使用"); const uniqueFilter = [...data1, ...data2].filter((v, i, self) => self.indexOf(v) === i); console.timeEnd("filter使用"); // 約1000ms(100倍遅い) console.log(uniqueSet.length === uniqueFilter.length); // true実務での推奨方針
// ✅ デフォルト:スプレッド構文(可読性重視)
const normal = [...array1, ...array2];
// ✅ 重複排除が必要:Set(プリミティブ型)
const unique = [...new Set([...array1, ...array2])];
// ✅ 重複排除が必要:Map(オブジェクト配列)
const map = new Map();
[...array1, ...array2].forEach(item => map.set(item.id, item));
const merged = Array.from(map.values());
// ✅ 大規模配列(10万要素以上)でクリティカル:concat
const huge = hugeArray1.concat(hugeArray2);
-
配列を結合したら型がany[]になってしまう。どう直す?
-
型推論が失敗している場合は、明示的に型アノテーションを付けることで解決します。
// ❌ 型推論が失敗する例 function mergeBadly(a: any[], b: any[]) { return [...a, ...b]; // 戻り値の型: any[] } const result1 = mergeBadly([1, 2], [3, 4]); // any[]型になってしまう // ✅ ジェネリクスを使って型安全に function mergeGeneric<T>(a: T[], b: T[]): T[] { return [...a, ...b]; } const result2 = mergeGeneric([1, 2], [3, 4]); // number[]型 const result3 = mergeGeneric(["a", "b"], ["c", "d"]); // string[]型 // ✅ 異なる型を結合する場合はUnion型で function mergeUnion<T, U>(a: T[], b: U[]): (T | U)[] { return [...a, ...b]; } const result4 = mergeUnion([1, 2], ["a", "b"]); // (number | string)[]型関数の戻り値を明示する
// ❌ 型が失われる例
function getData() {
const arr1 = [1, 2];
const arr2 = [3, 4];
return [...arr1, ...arr2]; // 型推論は効くが、複雑な場合は失敗することも
}
// ✅ 戻り値の型を明示
function getDataTyped(): number[] {
const arr1 = [1, 2];
const arr2 = [3, 4];
return [...arr1, ...arr2]; // number[]型が保証される
}
const data = getDataTyped(); // number[]型
-
push()で結合するのとスプレッド構文、どちらが良い?
-
元の配列を変更したくない場合はスプレッド構文、変更しても良い(メモリ効率重視)場合は
push()です。// スプレッド構文:元の配列は変更されない(イミュータブル) const original1 = [1, 2, 3]; const added1 = [...original1, 4, 5]; console.log(original1); // [1, 2, 3] - 変更されていない console.log(added1); // [1, 2, 3, 4, 5] // push():元の配列が変更される(ミュータブル) const original2 = [1, 2, 3]; original2.push(4, 5); console.log(original2); // [1, 2, 3, 4, 5] - 変更されている配列同士を結合する場合
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; // ❌ push()だけでは配列全体を追加できない arr1.push(arr2); // [1, 2, 3, [4, 5, 6]] - ネストしてしまう // ✅ push()にスプレッド構文を組み合わせる const arr3 = [1, 2, 3]; arr3.push(...arr2); // [1, 2, 3, 4, 5, 6] // ✅ スプレッド構文(新しい配列を生成) const arr4 = [...arr1, ...arr2]; // 元の配列は保持パフォーマンスとメモリの考慮
// メモリ効率重視:既存配列を変更 function appendInPlace<T>(target: T[], source: T[]): void { target.push(...source); // 新しい配列を作らない } const base = [1, 2, 3]; appendInPlace(base, [4, 5, 6]); console.log(base); // [1, 2, 3, 4, 5, 6] // イミュータブル重視:新しい配列を生成 function appendImmutable<T>(target: T[], source: T[]): T[] { return [...target, ...source]; // 元の配列は保持 } const original = [1, 2, 3]; const newArray = appendImmutable(original, [4, 5, 6]); console.log(original); // [1, 2, 3] - 変更されていない console.log(newArray); // [1, 2, 3, 4, 5, 6]実務での推奨
// ✅ デフォルト:スプレッド構文(イミュータブル、安全)
const result = [...array1, ...array2];
// ✅ 大規模配列でメモリがクリティカル:push()
const massive = [];
for (const chunk of largeDataChunks) {
massive.push(...chunk); // メモリ効率が良い
}
-
nullやundefinedが含まれる配列を安全に結合するには?
-
型ガード関数を使ってフィルタリングすることで、型安全に結合できます。
// 型ガード関数の定義 function isNotNullish<T>(value: T | null | undefined): value is T { return value !== null && value !== undefined; } const data1: (string | null)[] = ["apple", null, "banana"]; const data2: (string | undefined)[] = ["carrot", undefined, "potato"]; // 型ガードでフィルタリングしてから結合 const safeData: string[] = [...data1, ...data2].filter(isNotNullish); console.log(safeData); // ["apple", "banana", "carrot", "potato"] // 型安全に使用可能 safeData.forEach(item => { console.log(item.toUpperCase()); // エラーなし });詳しくは本記事の「型安全を維持する」セクションを参照してください。
-
二次元配列を一次元配列に変換して結合するには?
-
flat()メソッドを使うのが最もシンプルです。// 二次元配列
const nested1: number[][] = [[1, 2], [3, 4]];
const nested2: number[][] = [[5, 6], [7, 8]];
// 結合してフラット化
const flattened: number[] = [...nested1, ...nested2].flat();
console.log(flattened); // [1, 2, 3, 4, 5, 6, 7, 8]
// より深いネスト
const deep: number[][][] = [[[1, 2]], [[3, 4]]];
const flatDeep: number[] = deep.flat(2); // 深さを指定
console.log(flatDeep); // [1, 2, 3, 4]
// すべてフラット化
const allFlat: number[] = deep.flat(Infinity);
-
TypeScriptで配列の結合順序を保証する方法は?
-
スプレッド構文、
concat()、push()のいずれも順序を保証します。const first = ["a", "b"];
const second = ["c", "d"];
const third = ["e", "f"];
// すべて同じ順序で結合される
const result1 = [...first, ...second, ...third];
const result2 = first.concat(second, third);
const result3 = [...first];
result3.push(...second, ...third);
console.log(result1); // ["a", "b", "c", "d", "e", "f"]
console.log(result2); // ["a", "b", "c", "d", "e", "f"]
console.log(result3); // ["a", "b", "c", "d", "e", "f"]
// 順序を変えたい場合は明示的に
const reversed = [...third, ...second, ...first];
console.log(reversed); // ["e", "f", "c", "d", "a", "b"]
-
配列を結合してから特定の条件でフィルタリングする最適な書き方は?
-
メソッドチェーンを使うことで、読みやすく効率的に処理できます。
const numbers1 = [1, 2, 3, 4, 5];
const numbers2 = [4, 5, 6, 7, 8];
const numbers3 = [7, 8, 9, 10];
// 結合 → 重複排除 → フィルタリング → ソート
const result = [...new Set([...numbers1, ...numbers2, ...numbers3])]
.filter(num => num % 2 === 0) // 偶数のみ
.sort((a, b) => a - b); // 昇順ソート
console.log(result); // [2, 4, 6, 8, 10]
// オブジェクト配列の場合
type Product = {
id: number;
name: string;
price: number;
};
const products1: Product[] = [
{ id: 1, name: "A", price: 1000 },
{ id: 2, name: "B", price: 500 }
];
const products2: Product[] = [
{ id: 2, name: "B", price: 500 },
{ id: 3, name: "C", price: 1500 }
];
// 結合 → 重複排除 → フィルタリング
const map = new Map<number, Product>();
[...products1, ...products2].forEach(p => map.set(p.id, p));
const filtered = Array.from(map.values())
.filter(p => p.price >= 1000)
.sort((a, b) => b.price - a.price); // 価格降順
console.log(filtered);
// [
// { id: 3, name: "C", price: 1500 },
// { id: 1, name: "A", price: 1000 }
// ]
-
配列の結合と同時に型変換を行うには?
-
map()を組み合わせることで、結合と型変換を同時に行えます。// 文字列配列と数値配列を結合してすべて文字列に
const strings = ["a", "b", "c"];
const numbers = [1, 2, 3];
const allStrings: string[] = [
...strings,
...numbers.map(n => String(n))
];
console.log(allStrings); // ["a", "b", "c", "1", "2", "3"]
// オブジェクト配列の特定プロパティを抽出して結合
type User = { id: number; name: string };
type Product = { id: number; title: string };
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
const products: Product[] = [
{ id: 101, title: "Laptop" },
{ id: 102, title: "Mouse" }
];
// 両方からIDを抽出して結合
const allIds: number[] = [
...users.map(u => u.id),
...products.map(p => p.id)
];
console.log(allIds); // [1, 2, 101, 102]
-
ReactやVueなどでStateの配列を結合する際の注意点は?
-
イミュータブルな更新を心がけ、元の配列を変更しないようにします。
// React(TypeScript)での例
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState<string[]>(['Task 1', 'Task 2']);
// ✅ 正しい:新しい配列を生成
const addTodos = (newTodos: string[]) => {
setTodos([...todos, ...newTodos]); // イミュータブル
};
// ❌ 間違い:元の配列を変更してしまう
const addTodosBad = (newTodos: string[]) => {
todos.push(...newTodos); // ミュータブル - Reactが変更を検知できない
setTodos(todos);
};
return (
<button onClick={() => addTodos(['Task 3', 'Task 4'])}>
Add Tasks
</button>
);
}
// Vue 3(Composition API)での例
import { ref } from 'vue';
const items = ref<string[]>(['Item 1', 'Item 2']);
// ✅ 正しい:新しい配列を代入
function addItems(newItems: string[]) {
items.value = [...items.value, ...newItems];
}
// ❌ 間違い(ただしVueはこれでも動作するが、非推奨)
function addItemsBad(newItems: string[]) {
items.value.push(...newItems);
}
まとめ
TypeScriptで配列を結合する方法は多岐にわたりますが、型安全性を維持しながら、状況に応じた最適な手法を選択することが重要です。この記事で解説した内容を振り返りながら、実務での配列結合のベストプラクティスをまとめます。
状況別:最適な配列結合方法の選び方
実務で配列を結合する際は、以下のフローチャートに従って手法を選択することをおすすめします。
基本的な結合(型安全性重視)
// ✅ デフォルトの選択:スプレッド構文
const result = [...array1, ...array2];
// 理由:
// - 可読性が高い
// - モダンで直感的
// - 型推論が正確
// - 元の配列を変更しない(イミュータブル)
重複排除が必要な場合
// ✅ プリミティブ型:Set を使用
const unique = [...new Set([...array1, ...array2])];
// ✅ オブジェクト配列:Map を使用
const map = new Map();
[...array1, ...array2].forEach(item => map.set(item.id, item));
const merged = Array.from(map.values());
// 理由:
// - 計算量 O(n) で効率的(filterは O(n²))
// - 大規模データでも高速
// - 型安全性を維持
異なる型の配列を結合する場合
// ✅ Union型で明示的に型定義
type AllowedType = string | number;
const mixed: AllowedType[] = [...strings, ...numbers];
// または
const mixed: (string | number)[] = [...strings, ...numbers];
// 理由:
// - 予期しない型の混入を防げる
// - 後続処理で型ガードが使いやすい
// - 意図が明確になる
null/undefinedが混入する配列の場合
// ✅ 型ガード関数を使用
function isNotNullish<T>(value: T | null | undefined): value is T {
return value !== null && value !== undefined;
}
const safe: T[] = [...array1, ...array2].filter(isNotNullish);
// 理由:
// - TypeScriptの型システムに型の絞り込みが伝わる
// - filter(Boolean) では型が絞り込まれない
// - 型安全に後続処理が可能
大規模配列(10万要素以上)でパフォーマンスがクリティカルな場合
// ✅ concat() またはメモリ効率重視なら push()
const result1 = array1.concat(array2); // 新しい配列を生成
// またはメモリ効率重視
const base = [...array1];
base.push(...array2); // 既存配列を変更
// 理由:
// - わずかながらパフォーマンス上の優位性がある場合も
// - ただし通常はスプレッド構文で十分
// - 実測してから判断すること
多次元配列のフラット化
// ✅ flat() を使用
const flattened = [...nested1, ...nested2].flat();
// より深いネストの場合
const deepFlat = nested.flat(Infinity);
// 理由:
// - シンプルで読みやすい
// - 型推論も正確
// - パフォーマンスも良好
TypeScriptならではの型安全性のメリット
配列結合において型安全性を維持することは、以下のような大きなメリットをもたらします。
1. コンパイル時のエラー検出
// ❌ JavaScriptでは実行時エラー
const mixed = [...strings, ...numbers, ...booleans];
mixed.forEach(item => {
console.log(item.toUpperCase()); // booleanでエラー(実行時)
});
// ✅ TypeScriptでは型チェックで事前検出
type AllowedType = string | number;
const safe: AllowedType[] = [...strings, ...numbers];
// const unsafe: AllowedType[] = [...strings, ...booleans]; // コンパイルエラー
safe.forEach(item => {
if (typeof item === "string") {
console.log(item.toUpperCase()); // 型ガードで安全
}
});
2. IDEの強力な補完とリファクタリング支援
type User = {
id: number;
name: string;
email: string;
};
const users: User[] = [...users1, ...users2];
// IDEが各プロパティを正確に認識
users.forEach(user => {
console.log(user.); // ← ここで id, name, email が補完される
});
// リファクタリング時も安全
// プロパティ名を変更すると、すべての参照箇所でエラーが表示される
3. ドキュメントとしての型定義
// 関数シグネチャが明確なドキュメントになる
function mergeUserData(
activeUsers: User[],
inactiveUsers: User[]
): User[] {
return [...activeUsers, ...inactiveUsers];
}
// 呼び出し側も型安全
const allUsers = mergeUserData(active, inactive);
// allUsersは確実にUser[]型
4. チーム開発での安全性向上
// チームメンバーが誤った型を渡すとコンパイルエラー
function processData(items: (string | number)[]): void {
// 処理
}
// ✅ OK
processData([...strings, ...numbers]);
// ❌ コンパイルエラー
// processData([...strings, ...booleans]);
// 型チェックで未然に防げる
保守性の高いコードを書くための指針
TypeScriptで配列を結合する際、以下の指針に従うことで、保守性の高いコードを実現できます。
1. 型を明示的に定義する
// ❌ 暗黙的な型推論に頼りすぎ
function merge(a, b) {
return [...a, ...b];
}
// ✅ 明示的な型定義
function merge<T>(a: T[], b: T[]): T[] {
return [...a, ...b];
}
2. イミュータブルな操作を優先する
// ❌ 元の配列を変更
function addItems(base: string[], items: string[]): void {
base.push(...items);
}
// ✅ 新しい配列を返す
function addItems(base: string[], items: string[]): string[] {
return [...base, ...items];
}
3. 型ガードを活用する
// ❌ 型の絞り込みが不十分
const data = [...array1, ...array2].filter(Boolean);
// ✅ 型ガードで明確に
const data = [...array1, ...array2].filter(isNotNullish);
4. 関数を小さく、単一責任に保つ
// ❌ 1つの関数で複数の責任
function processAndMerge(a: User[], b: User[]): User[] {
const filtered = a.filter(u => u.active);
const merged = [...filtered, ...b];
return merged.sort((x, y) => x.id - y.id);
}
// ✅ 責任を分離
function filterActive(users: User[]): User[] {
return users.filter(u => u.active);
}
function mergeUsers(a: User[], b: User[]): User[] {
return [...a, ...b];
}
function sortById(users: User[]): User[] {
return [...users].sort((x, y) => x.id - y.id);
}
// 組み合わせて使う
const result = sortById(mergeUsers(filterActive(users1), users2));
5. パフォーマンスは実測してから最適化する
// まずは読みやすさ優先
const result = [...array1, ...array2];
// パフォーマンス問題が確認されてから最適化
// console.time() / console.timeEnd() で計測
// 必要に応じて concat() や Map に変更
次のステップ:さらなる学習のために
TypeScriptでの配列操作をさらに深めるために、以下のトピックも学習することをおすすめします。
関連する配列メソッド
// reduce: 配列を任意の値に集約
const sum = numbers.reduce((acc, curr) => acc + curr, 0);
// flatMap: マップとフラット化を同時に
const tags = users.flatMap(user => user.tags);
// groupBy(将来的に標準化予定): グループ化
// 現在はMap.groupBy()として提案中
より高度な型操作
// Mapped Types
type ReadonlyArray<T> = {
readonly [K in keyof T]: T[K];
};
// Conditional Types
type NonNullable<T> = T extends null | undefined ? never : T;
// Template Literal Types
type EventName = `on${string}`;
実務パターンの習得
- Lodashなどのユーティリティライブラリとの比較
- 関数型プログラミングのパターン(map, filter, reduce の組み合わせ)
- パフォーマンスチューニングの実践
最後に
TypeScriptで配列を結合する際の最も重要なポイントは、型安全性と可読性のバランスです。
- 基本はスプレッド構文を使う
- 重複排除にはSetやMapを活用する
- 型ガードで null/undefined を安全に除外する
- 明示的な型定義で意図を明確にする
- パフォーマンスは実測してから最適化する
これらの原則に従うことで、バグが少なく、保守性の高い、チーム開発に適したコードを書くことができます。
型安全な配列結合をマスターすることは、TypeScriptの力を最大限に引き出す第一歩です。この記事で学んだテクニックを実務で活用し、より堅牢なアプリケーション開発を実現してください。
格安ドメイン取得サービス─ムームードメイン─

