TypeScriptのfor文のベストプラクティス

TypeScriptのfor文のベストプラクティス

この記事ではTypeScriptのfor文のベストプラクティスについて説明します。

YouTube Video

TypeScriptのfor文のベストプラクティス

for文は、繰り返し処理を行うための基本的かつ強力な構文です。TypeScriptでは、型安全性を活かした使い方や、可読性・保守性を意識した書き方を実践することで、エラーの少ない効率的なコードを書くことができます。

適切なループタイプの選択

TypeScriptにはいくつかのループ構文があり、それぞれ異なるユースケースに適しています。適切なループを選ぶことは、コードの明確さとパフォーマンスにおいて重要です。

標準的なfor

1for (let i = 0; i < 10; i++) {
2    console.log(i);
3}

標準的なfor文は、反復処理のインデックスを正確に制御する必要がある場合に最適です。

ベストプラクティス
1const maxIterations = 10;
2for (let i = 0; i < maxIterations; i++) {
3    console.log(i);
4}

for文を記述する際には、次のような点に注意することでより安全で読みやすいコードになります。

  • インデックス変数にletを使用する
    • varではなくletを使うことでスコープがブロックに限定され、意図しない動作を防げます。
  • 定数や説明的な変数名を使ってループ境界を明示する
    • マジックナンバーを避け、意味のある変数名を使うことで可読性が向上します。

for...ofループ

1const array = [1, 2, 3];
2for (let value of array) {
3    console.log(value);
4}

for...ofループは、配列や文字列のようなイテラブルオブジェクトを反復処理するのに適しています。

ベストプラクティス
1const array = [1, 2, 3];
2for (const value of array) {
3    console.log(value);
4}

for...ofループを記述する際には、次の点に注意することで、より安全なコードになります。

  • ループ変数にconstを使用する
    • ループ中に値を変更しない場合はconstを使用することで、誤った再代入を防ぐことができます。

for...inループ

1const obj = { a: 1, b: 2, c: 3 };
2for (const key in obj) {
3    console.log(`${key}: ${obj[key]}`);
4}

for...inループは、オブジェクトの列挙可能なプロパティを反復処理します。

ベストプラクティス
1const obj = { a: 1, b: 2, c: 3 };
2for (const key in obj) {
3    if (obj.hasOwnProperty(key)) {
4        console.log(`${key}: ${obj[key]}`);
5    }
6}

for...inループを記述する際には、次の点を考慮できます。

  • プロパティをフィルタリングする
    • 継承されたプロパティを避ける必要がある場合、hasOwnPropertyを使用できます。
  • 配列にはfor...inを使わない 配列ではfor...inを避けましょう。配列のインデックスではないプロパティも含め、すべての列挙可能なプロパティを反復処理してしまう可能性があります。

forEachメソッド

1const array = [1, 2, 3];
2array.forEach((value, index) => {
3    console.log(`Index: ${index}, Value: ${value}`);
4});

配列の反復処理では、forEachを使用すると簡潔でインデックス管理が不要です。

ベストプラクティス

forEachメソッドを利用する際には、次の点を考慮できます。

  • アロー関数を利用する
    • 簡潔なアロー関数を使用して可読性を向上させましょう。
  • 中断を避ける
    • forEachbreakcontinueをサポートしていません。必要に応じてfor...offorループを使用しましょう。

型の安全性とエラー防止

TypeScriptの型システムを活用することで、反復処理中のランタイムエラーを防ぐことができます。

ループ変数に厳密な型を指定する

1const items = [1, 2, 3];
2items.forEach(item => {
3    console.log(item * 2);
4});
1const items: number[] = [1, 2, 3];
2items.forEach((item: number) => {
3    console.log(item * 2);
4});

このように、ループ変数に明示的に型を指定することで、型の不一致を早期に検出できます。

暗黙のanyを避ける

1{
2  "compilerOptions": {
3    "noImplicitAny": true
4  }
5}

また、tsconfig.jsonnoImplicitAnyを有効にすることで、型指定のない変数に暗黙のany型が割り当てられるのを防げます。

必要に応じてReadonlyArrayを使用する

1const numbers: ReadonlyArray<number> = [1, 2, 3];
2for (const value of numbers) {
3    console.log(value);
4}

変更されるべきでない配列を反復処理する場合は、ReadonlyArrayを使用できます。

パフォーマンスの考慮点

大規模なデータセットを処理するループや頻繁に実行されるループでは、効率性が重要です。

最適なループの実装方法を選択する

ループの実装方法にはさまざまな種類があり、それぞれに可読性や実行効率の違いがあります。

 1// Prepare input data (an array from 1 to 1000000)
 2const input: number[] = Array.from({ length: 1000000 }, (_, i) => i + 1);
 3
 4// --- for ---
 5console.time('for loop');
 6const squaresFor: number[] = [];
 7for (let i = 0; i < input.length; i++) {
 8    squaresFor.push(input[i] * input[i]);
 9}
10console.timeEnd('for loop');
11
12// --- while ---
13console.time('while loop');
14const squaresWhile: number[] = [];
15let i: number = 0;
16while (i < input.length) {
17    squaresWhile.push(input[i] * input[i]);
18    i++;
19}
20console.timeEnd('while loop');
21
22// --- for-of ---
23console.time('for-of loop');
24const squaresForOf: number[] = [];
25for (const num of input) {
26    squaresForOf.push(num * num);
27}
28console.timeEnd('for-of loop');
29
30// --- forEach ---
31console.time('forEach loop');
32const squaresForEach: number[] = [];
33input.forEach((num: number): void => {
34    squaresForEach.push(num * num);
35});
36console.timeEnd('forEach loop');
37
38// --- map ---
39console.time('map');
40const squaresMap: number[] = input.map((value: number): number => value * value);
41console.timeEnd('map');

実行環境によって効率は異なりますが、たとえば100万回のループを実行する場合には、その差が顕著に現れることもあります。保守性やパフォーマンスを考慮して最適なループの方法を選びましょう。

ネイティブの反復処理メソッドを使用する

 1const squares = [1, 2, 3].map(value => value * value);
 2console.log(squares);
 3
 4const numbers = [1, 2, 3, 4, 5, 6];
 5const evenNumbers = numbers.filter(value => value % 2 === 0);
 6console.log(evenNumbers); // [2, 4, 6]
 7
 8const squaredEvens = numbers
 9    .filter(value => value % 2 === 0) // Keep only even numbers
10    .map(value => value * value);     // Square the remaining values
11
12console.log(squaredEvens); // [4, 16, 36]

このように、mapfilterのようなメソッドは、より読みやすい場合があります。

読みやすさのためにfor...ofを優先する

パフォーマンスは限られた局面で優先されますが、読みやすいコードを書くことはより大切です。例えば、for...ofを優先することで可読性を向上できます。

1const fruits = ["apple", "banana", "cherry"];
2
3for (let i = 0; i < fruits.length; i++) {
4    console.log(`${i}: ${fruits[i]}`);
5}
ベストプラクティス
1const fruits = ["apple", "banana", "cherry"];
2
3for (const [index, fruit] of fruits.entries()) {
4    console.log(`${index}: ${fruit}`);
5}

for...ofループを優先的に使うことで、読みやすく、エラーが少ないコードを記述できます。この例のように、配列のインデックスも必要なケースには、entries()for...ofの組み合わせも有効です。

よくある落とし穴の回避

反復処理中のコレクションの変更

1const array = [1, 2, 3];
2for (const value of [...array]) {
3    if (value === 2) {
4        array.push(4); // Avoid this!
5    }
6}
7console.log(array);

反復処理中に配列を変更すると予期しない動作を引き起こす可能性があります。必要であればコピーを使用しましょう。

境界ケースを考慮する

1const array: number[] = [];
2for (const value of array) {
3    console.log(value); // No output, but no errors
4}

このコードは問題なく動作しますが、空の配列について処理が必要な場合には次のように改善できます。

1const array: number[] = [];
2if (array.length === 0) {
3    console.log("The array is empty.");
4} else {
5    for (const value of array) {
6        console.log(value);
7    }
8}

境界ケースを考慮することで、後続するコードでのエラーを防ぐことができます。

結論

TypeScriptにおけるfor文を習得するには、さまざまなループ構文を理解し、型安全なプラクティスを遵守し、パフォーマンスを最適化することが必要です。これらのベストプラクティスは、よりクリーンで信頼性が高く、保守性の高いコードを記述する役に立ちます。

YouTubeチャンネルでは、Visual Studio Codeを用いて上記の記事を見ながら確認できます。 ぜひYouTubeチャンネルもご覧ください。

YouTube Video