【TypeScript】値渡しの方法を10選の実例コードを交えて解説

TypeScriptを使った値渡しのイラストTypeScript
この記事は約30分で読めます。

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

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

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

基本的な知識があればサンプルコードを活用して機能追加、目的を達成できるように作ってあります。

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

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

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

はじめに

TypeScriptは近年、Web開発者の間で非常に人気が高まってきた言語です。

特にJavaScriptの上位互換として、静的型付けの機能を持っており、大規模なプロジェクトや複雑なアプリケーションの開発に適しています。

この記事では、TypeScriptを使用して「値渡し」を学ぶ10の方法を初心者向けに詳しく解説していきます。

それぞれの方法にはサンプルコードが含まれており、そのコードの説明とともに、その実行結果がどのようになるかも紹介します。

また、注意点や応用例、カスタマイズ例も交えながら解説していきます。

「値渡し」はプログラミングの基本的な概念の一つであり、TypeScriptでもその重要性は変わりません。

正確に理解し、適切に実装することで、安全で効率的なコードを書くことができます。

それでは、TypeScriptでの「値渡し」の理解を深めていきましょう。

●TypeScriptとは

TypeScriptはMicrosoftが開発したオープンソースのプログラミング言語であり、JavaScriptの上位互換言語として知られています。

JavaScriptの全てのコードはTypeScriptとしても動作しますが、TypeScriptには静的型付けの機能やインターフェイス、ジェネリクスなど、JavaScriptにはない機能が多数含まれています。

●値渡しとは

値渡しは、関数やメソッドに引数として値を渡すときの動作を指します。

具体的には、関数に引数として渡された値が、関数内で変更されても、関数の外側の元の変数の値は変わらないという特徴があります。

この性質により、関数内での変数の変更が外部に影響を及ぼすことなく、安全にコードを書くことができます。

●TypeScriptでの値渡しの方法10選

○サンプルコード1:基本的な値渡し

このコードでは、整数を使って基本的な値渡しを行うコードを表しています。

この例では、関数addに2つの整数を引数として渡し、その合計を返すという処理をしています。

   function add(a: number, b: number): number {
       return a + b;
   }

   const num1 = 5;
   const num2 = 3;
   const result = add(num1, num2);
   console.log(`合計は${result}です。`);

上記のコードを実行すると、コンソールに「合計は8です。」と表示されます。

このとき、関数add内での変数abの変更は、外部の変数num1num2に影響を与えません。

○サンプルコード2:オブジェクトを値渡しする方法

JavaScriptをベースにしたTypeScriptは、オブジェクト指向の特性を持つプログラム言語であり、オブジェクトの扱い方は開発者の日常的な作業の一部です。

その中でも「値渡し」という概念は、TypeScriptでオブジェクトを扱う際の基本中の基本とも言えるものです。

ここでは、TypeScriptでのオブジェクトの値渡しに関して、詳細なサンプルコードを交えて解説いたします。

オブジェクトはキーと値のペアの集合体として定義されます。

例えば、下記のコードは名前と年齢を持つ人物のオブジェクトのサンプルコードです。

const person = {
    name: "山田太郎",
    age: 30
};

このコードではpersonという名前のオブジェクトが定義されており、その中にはnameageという2つのキーが存在します。

では、このオブジェクトを別のオブジェクトに値渡しする方法を見ていきましょう。

const person = {
    name: "山田太郎",
    age: 30
};

const newPerson = {...person};

この例では、newPersonオブジェクトにpersonオブジェクトの内容が値渡しでコピーされます。

ここでの...はスプレッド構文と呼ばれ、オブジェクトや配列の要素を展開する際に使用します。

上記のコードを実行すると、newPersonオブジェクトはpersonオブジェクトと全く同じ内容を持つ別のオブジェクトとして作成されます。

しかし、重要な点として、newPersonpersonは異なるメモリアドレスを持つ2つの異なるオブジェクトであり、一方を変更してももう一方に影響は出ません。

○サンプルコード3:配列を値渡しする方法

TypeScriptでの配列の取り扱いは、JavaScriptと非常に似ています。

ただ、型が導入されているため、より安全なコードを書くことができます。

配列の値渡しに関して、JavaやC++といった言語からの移行者や初学者にとっては、少し取っ付きにくい部分もあるかもしれません。

そこで、ここでは、TypeScriptでの配列を値渡しする方法について解説していきます。

このコードでは、TypeScriptでの配列の値渡しの基本的な手法を用いて、配列のコピーを作成しています。

この例では、元の配列を変更しても、新しく作成した配列には影響がないことを表しています。

// TypeScriptでの配列の値渡しの例
function copyArray(original: number[]): number[] {
  // 配列をスプレッド構文を用いてコピー
  const copied = [...original];
  return copied;
}

const array1 = [1, 2, 3, 4, 5];
const array2 = copyArray(array1);

// array1の内容を変更
array1[0] = 99;

console.log(array1); // [99, 2, 3, 4, 5]
console.log(array2); // [1, 2, 3, 4, 5]

上記のサンプルコードを実行すると、array1の最初の要素を変更しても、array2には何の影響も及ぼしていないことがわかります。

つまり、copyArray関数は、配列の要素を値渡しして新しい配列を作成しているのです。

さらに、配列の内容を確認すると、array1[99, 2, 3, 4, 5]と変わっていますが、array2[1, 2, 3, 4, 5]と変わっていないことがわかります。

これにより、配列のコピーが正確に行われ、値渡しが成功していることが確認できます。

注意点として、スプレッド構文は浅いコピーを行います。

したがって、配列の要素がオブジェクトや別の配列の場合、その中身まではコピーされず、参照がコピーされるので注意が必要です。

応用例として、次のように2次元配列を値渡しでコピーする方法も考えられます。

function copy2DArray(original: number[][]): number[][] {
  return original.map(arr => [...arr]);
}

const matrix1 = [[1, 2], [3, 4], [5, 6]];
const matrix2 = copy2DArray(matrix1);

matrix1[0][0] = 99;

console.log(matrix1); // [[99, 2], [3, 4], [5, 6]]
console.log(matrix2); // [[1, 2], [3, 4], [5, 6]]

このコードでは、copy2DArray関数を使って2次元配列を値渡しでコピーしています。

この例でも、元の配列matrix1の内容を変更しても、コピーした配列matrix2には影響がないことが確認できます。

○サンプルコード4:関数内での値渡し

関数を使用する際、特にTypeScriptを始めたばかりの初心者の方にとって、関数の引数としてデータをどのように渡すかは、とても大切な知識点となります。

ここでは、関数内での「値渡し」の仕組みに焦点を当て、その方法と動作を詳しく解説していきます。

まずは、基本的なサンプルコードをご覧ください。

// 関数定義時に引数を設定
function displayMessage(message: string): void {
    // 引数の内容をコンソールに表示
    console.log(message);
}

// 関数を呼び出し時に文字列を渡す
displayMessage("こんにちは、TypeScript!");

このコードでは、文字列型の引数messageを持つ関数displayMessageを定義しています。

この例では、関数を呼び出す際に文字列"こんにちは、TypeScript!"を引数として渡し、その内容をコンソールに表示しています。

この場合、関数displayMessageの引数messageは、関数を呼び出す際に渡された値をコピーして受け取ります。

したがって、関数内でmessageの値を変更しても、元の値は影響を受けません。

関数内で引数の値を変更する例を紹介します。

function modifyValue(value: number): number {
    // 引数の値を2倍にする
    value = value * 2;
    return value;
}

const originalValue = 5;
const newValue = modifyValue(originalValue);
console.log(originalValue); // 5
console.log(newValue);      // 10

この例では、数値型の引数valueを2倍にして返す関数modifyValueを定義しています。

関数を呼び出す際に、変数originalValueの値を引数として渡しています。

しかし、関数内で引数の値を変更しても、変数originalValueの値は変わりません。

このため、コンソールに表示されるのは、originalValueは5、newValueは10という結果になります。

この動作は、関数の引数が「値渡し」されているためです。

値渡しでは、関数に渡されるデータはそのデータのコピーとして関数内で扱われます。

したがって、関数内で引数の値を変更しても、元の変数の値には影響を及ぼしません。

こちらの動作を理解し、適切に関数の引数を扱うことで、意図しないデータの変更やエラーを防ぐことができます。

特にTypeScriptでは、型の安全性を保つためにこのような知識がとても重要です。

次に、注意点として考慮すべき事項について解説します。

TypeScriptにおける関数の引数は、基本的なデータ型(例:数値、文字列、真偽値など)については前述の通り「値渡し」となります。

しかし、オブジェクトや配列の場合は少し留意が必要です。

オブジェクトや配列を関数の引数として渡すとき、それは「参照渡し」となります。

このため、関数内でオブジェクトや配列の内容を変更すると、元のオブジェクトや配列も変更されてしまいます。

function addItemToArray(arr: number[]): void {
    // 配列に新しい要素を追加
    arr.push(100);
}

const myArray = [1, 2, 3];
addItemToArray(myArray);
console.log(myArray);  // [1, 2, 3, 100]

このコードの例では、addItemToArray関数内で配列arrに新しい要素を追加しています。

関数を呼び出す際にmyArrayを渡していますが、関数内での変更がmyArrayにも反映されているため、コンソールには[1, 2, 3, 100]と表示されます。

○サンプルコード5:複雑なデータ構造の値渡し

TypeScriptでは、基本的なデータ型の他にも複雑なデータ構造の値渡しの方法が存在します。

ここでは、複雑なデータ構造を持つオブジェクトの中のオブジェクト、またはネストされた配列の値渡しの方法を取り上げます。

まず、オブジェクトの中のオブジェクトの例を見てみましょう。

// オブジェクト内にオブジェクトを持つデータ構造の定義
interface NestedObject {
    data: {
        value: number;
    };
}

function doubleNestedValue(obj: NestedObject): NestedObject {
    // 引数のオブジェクトのvalueの値を2倍にして、新しいオブジェクトを返す
    return {
        data: {
            value: obj.data.value * 2
        }
    };
}

const myObj: NestedObject = {
    data: {
        value: 5
    }
};

const doubledObj = doubleNestedValue(myObj);

// myObjの中身は変更されていない
console.log(myObj.data.value);  // 5
// doubledObjの中身は2倍になっている
console.log(doubledObj.data.value);  // 10

このコードでは、NestedObjectというインターフェースを使って、オブジェクト内にオブジェクトを持つデータ構造を定義しています。

そして、このネストされたオブジェクトのvalueを2倍にして、新しいオブジェクトを生成する関数doubleNestedValueを定義しています。

次に、ネストされた配列の例を見てみましょう。

function doubleNestedArray(arr: number[][]): number[][] {
    // 引数の配列の各要素(配列)の最初の値を2倍にして新しい配列を返す
    return arr.map(subArr => [subArr[0] * 2]);
}

const myArray = [[1], [2], [3]];
const doubledArray = doubleNestedArray(myArray);

// myArrayの中身は変更されていない
console.log(myArray[0][0]);  // 1
// doubledArrayの中身は2倍になっている
console.log(doubledArray[0][0]);  // 2

このコードでは、ネストされた配列(2次元配列)の各要素(配列)の最初の値を2倍にして、新しい2次元配列を生成する関数doubleNestedArrayを定義しています。

これらの例からわかるように、TypeScriptでは複雑なデータ構造の値渡しも簡単に行うことができます。

ただし、ネストされたデータ構造を扱う際には、関数内でのデータ変更が元のデータに影響しないように注意する必要があります。

特に、オブジェクトや配列の中身を直接変更する場合、元のデータも変更される可能性が高くなります。

このような問題を回避するために、新しいデータ構造を生成して返す方法や、データのコピーを作成してから操作する方法が推奨されます。

これにより、意図しないデータの変更やバグを防ぐことができます。

最後に、ネストされたオブジェクトや配列を効果的に扱うためのカスタマイズ例を紹介します。

// ネストされたオブジェクトの深いコピーを作成する関数
function deepClone(obj: any): any {
    return JSON.parse(JSON.stringify(obj));
}

const obj = {
    data: {
        value: 5
    }
};

const clonedObj = deepClone(obj);
clonedObj.data.value = 10;

// objの中身は変更されていない
console.log(obj.data.value);  // 5
// clonedObjの中身は変更されている
console.log(clonedObj.data.value);  // 10

上記のdeepClone関数は、ネストされたオブジェクトの深いコピーを作成するためのものです。

これを使用することで、オブジェクトの構造を保持しつつ、新しいオブジェクトを生成して操作することができます。

これにより、元のオブジェクトに影響を与えることなく、安全にデータ操作を行うことが可能となります。

○サンプルコード6:非同期処理での値渡し

非同期処理は、JavaScriptの中心的な特性であり、TypeScriptもこれを完全にサポートしています。

Promiseやasync/awaitなどの非同期処理を用いる際に、関数への値渡しの方法と注意点を理解することは非常に重要です。

このコードでは、Promiseを使って非同期的にデータを取得し、その後の処理でそのデータを引数として受け取る方法を表しています。

この例では、非同期関数fetchDataを使ってデータを取得し、その後の処理で取得したデータを2倍にするdoubleValue関数に渡しています。

// 非同期に数値を取得する関数
function fetchData(): Promise<number> {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(5);
        }, 1000);
    });
}

// 値を2倍にする関数
function doubleValue(value: number): number {
    return value * 2;
}

// fetchDataで取得した値をdoubleValueに渡す
fetchData().then(data => {
    const result = doubleValue(data);
    console.log(result);  // 10と表示される
});

この例から、非同期処理で取得した値を他の関数に値渡しする際には、Promiseのthenメソッドを使用して、非同期処理が完了した後の結果を取得し、その結果を次の関数に渡すことができることがわかります。

次に、async/await構文を使用した場合の例を紹介します。

async function processValue() {
    // fetchDataの結果を待ってから取得
    const data = await fetchData();
    const result = doubleValue(data);
    console.log(result);  // 10と表示される
}

processValue();

async/awaitを使用すると、非同期処理を簡潔に書くことができます。

この例では、awaitを使ってfetchData関数の結果を待ってから、その結果をdoubleValue関数に渡して処理しています。

注意点として、非同期関数内でエラーが発生した場合、それを適切にキャッチして処理する必要があります。

非同期処理でのエラーハンドリングの例を紹介します。

async function processWithErrorHandling() {
    try {
        const data = await fetchData();
        const result = doubleValue(data);
        console.log(result);
    } catch (error) {
        console.error('データの処理中にエラーが発生しました:', error);
    }
}

processWithErrorHandling();

この例では、非同期処理の中でエラーが発生する可能性がある部分をtry/catch構文で囲み、エラーが発生した場合にはキャッチしてエラーメッセージを表示するようにしています。

応用例として、非同期処理で取得した複数の値をまとめて処理する方法もあります。

Promise.allを使用した例を紹介します。

function fetchData2(): Promise<number> {
    return new Promise((resolve) => {
        setTimeout(() => {
            resolve(7);
        }, 500);
    });
}

async function processMultipleValues() {
    const [data1, data2] = await Promise.all([fetchData(), fetchData2()]);
    console.log(doubleValue(data1), doubleValue(data2));  // 10と14を表示
}

processMultipleValues();

この例では、2つの非同期関数fetchDatafetchData2の結果を同時に待って、その後に取得した2つの値をdoubleValue関数で処理しています。

このように、Promise.allを使用することで、複数の非同期処理を効率的にまとめて処理することができます。

○サンプルコード7:ジェネリクスを使った値渡し

TypeScriptでのプログラミングを進める中で、ジェネリクスは欠かせない要素の一つと言えるでしょう。

ジェネリクスは、型を変数として扱うことで、より柔軟かつ再利用可能なコードを書くための仕組みです。

ここでは、ジェネリクスを使って値を渡す方法を学びます。

このコードでは、ジェネリクスを使った関数を表しています。

この例では、ジェネリクスを使用して異なる型の値を受け取り、そのまま返す関数を定義しています。

// ジェネリクスを使用した関数の定義
function passValue<T>(value: T): T {
    // コメント:受け取った値をそのまま返す
    return value;
}

// 使用例
const passedNumber = passValue<number>(123);
const passedString = passValue<string>("TypeScript");

こちらのコードでは、関数passValueがジェネリクス<T>を使用しており、この関数はどんな型の値も受け取ることができます。

使用例として、数値と文字列の2種類の値を渡して関数を呼び出しています。

実際にこのコードを実行すると、受け取った数値と文字列がそのまま返されます。

ジェネリクスを活用することで、様々な型に対応した関数やクラスを作成することが可能になります。

しかし、この仕組みを適切に使用するためには、ジェネリクスの基本的な概念や書き方をしっかりと理解しておくことが大切です。

また、ジェネリクスを使用する際には、特定の型に制約をつけることも可能です。

例えば、特定のインターフェースを実装したクラスのみを許容するようなジェネリクスの定義も可能です。

ジェネリクスに制約をつける一例を紹介します。

interface HasName {
    name: string;
}

function printName<T extends HasName>(obj: T) {
    console.log(obj.name);
}

const person = { name: "Tanaka", age: 30 };
printName(person);  // Tanakaと表示される

上記のコードでは、HasNameというインターフェースを持つオブジェクトのみを受け入れるprintName関数を定義しています。

このようにジェネリクスを用いることで、柔軟に型を制約しつつ、再利用可能な関数やクラスを作成することができます。

○サンプルコード8:TypeScriptのモジュールと値渡し

JavaScriptの進化版ともいえるTypeScript。TypeScriptはモジュールという強力な機能を持っており、これを利用することで、複数のファイル間で関数や変数を共有することができます。

今回は、このTypeScriptのモジュールと、それを使った値渡しについて詳しく見ていきましょう。

モジュールとは、コードの再利用を容易にするための機能です。

これにより、1つのファイルに記述した関数や変数を、別のファイルで利用することができるようになります。

では、TypeScriptでモジュールを使用して、値渡しを実装するサンプルコードを見てみましょう。

// dataModule.ts
export const data = "Hello from Module";

// main.ts
import { data } from './dataModule';

console.log(data); // "Hello from Module"

このコードでは、dataModule.tsというモジュール内でdataという文字列を定義し、これをexportしています。

一方、main.tsではこのdataをimportして、console.logで表示しています。

この例では、dataModule.tsからmain.tsへ値を渡しています。

値渡しの際、モジュールを介することで、値の管理がしやすくなり、大規模なプロジェクトでも効率的に開発を進めることができます。

しかし、このモジュールを利用する際の注意点として、モジュール名が重複しないように注意が必要です。

モジュール名が重複すると、期待する動作をしないことがあるので、常に一意な名前をつけるよう心がけましょう。

次に、このモジュールをカスタマイズして、より高度な値渡しを実現する方法を紹介します。

// valueModule.ts
export function valueFunction(num: number): number {
    return num * 2;
}

// app.ts
import { valueFunction } from './valueModule';

const result = valueFunction(5);
console.log(result); // 10

上記のコードでは、valueModule.tsというファイルに、数字を2倍にする関数valueFunctionを定義し、これをexportしています。

そして、app.tsではこの関数をimportして使用しています。

この例では、valueFunctionを使って、数字5を2倍にし、その結果として10を得ています。

○サンプルコード9:外部ライブラリを用いた値渡し

TypeScriptはJavaScriptのスーパーセットであるため、多くのJavaScriptのライブラリやフレームワークが利用可能です。

そのため、これらの外部ライブラリを利用して値渡しを行う際には、TypeScriptの特性を理解しつつ、各ライブラリの独自の機能や特性を活用することが求められます。

ここでは、外部ライブラリ「lodash」を用いて、深いコピー(ディープコピー)を行い、オブジェクトの値渡しを実現する方法を解説します。

まず、外部ライブラリとして「lodash」をプロジェクトにインストールする必要があります。

下記のコマンドでインストールできます。

npm install lodash @types/lodash

このコードでは、npmを使ってlodashライブラリとその型定義をインストールしています。

これにより、TypeScriptでlodashを安全に使用できるようになります。

次に、lodashcloneDeep関数を使用してオブジェクトをディープコピーするサンプルコードを表します。

import _ from 'lodash';

const originalObject = {
    name: 'Taro',
    address: {
        city: 'Tokyo',
        zip: '100-0001'
    }
};

// オブジェクトのディープコピー
const copiedObject = _.cloneDeep(originalObject);

// コピー先のオブジェクトのプロパティを変更
copiedObject.address.city = 'Osaka';

// 元のオブジェクトのプロパティは変わらない
console.log(originalObject.address.city); // Tokyo
console.log(copiedObject.address.city);   // Osaka

このコードでは、lodashcloneDeep関数を使ってoriginalObjectを完全にコピーした新しいオブジェクトを作成しています。

この例では、originalObjectaddress.cityを変更せず、copiedObjectaddress.cityのみを変更しています。

その結果、元のオブジェクトは変更されずに、コピーしたオブジェクトのみが変更されます。

外部ライブラリを用いることで、TypeScriptの機能だけでは難しかったディープコピーなどの複雑な操作を簡単に実現できるのが大きな利点です。

ただし、外部ライブラリを使用する際には、そのライブラリのドキュメントやチュートリアルを参照し、正しく使い方を理解して使用することが必要です。

○サンプルコード10:高度なTypeScriptの機能を用いた値渡し

TypeScriptはJavaScriptのスーパーセットとして、静的型付けや高度な型システムなどの機能を持っています。

これにより、より厳格なコーディングと高度なプログラミングパターンが可能となります。

ここでは、TypeScriptの高度な機能を活用した値渡しの方法を解説します。

このコードでは、TypeScriptの高度な型システムを使って関数の引数として複数の型を許容するコードを表しています。

この例では、関数processDataを定義して、引数としてstringまたはnumberのいずれかの型を受け取ることができるようにしています。

// 高度な型を使用した関数の定義
function processData(input: string | number): void {
    if (typeof input === "string") {
        console.log(`入力された文字列は ${input} です。`);  // 文字列として処理
    } else {
        console.log(`入力された数字は ${input} です。`);  // 数値として処理
    }
}

// 関数の呼び出し
processData("TypeScript");
processData(2023);

このコードを実行すると、processData関数は、与えられた引数が文字列か数値かに応じて異なる処理を行います。

最初の呼び出しでは、文字列”TypeScript”を引数として渡しており、結果として「入力された文字列は TypeScript です。」と表示されます。

次に、数値2023を引数として渡しており、結果として「入力された数字は 2023 です。」と表示されます。

注意点として、string | numberのような型は「union型」と呼ばれ、複数の型のいずれかを許容することができます。

しかし、union型を使う際は、関数内での型のチェックが必要になることが多いので注意が必要です。

また、TypeScriptの高度な機能として、ジェネリクスや型エイリアスなども利用できます。

これにより、より柔軟かつ厳格な型定義や関数の実装が可能となります。

応用例として、特定の型の配列を受け取る関数を考えてみましょう。

下記のサンプルコードでは、ジェネリクスを利用して、任意の型Tの配列を受け取り、その配列の長さを返す関数を実装しています。

// ジェネリクスを使用した関数の定義
function getLength<T>(arr: T[]): number {
    return arr.length;
}

// 関数の呼び出し
const numbers: number[] = [1, 2, 3, 4];
const result = getLength(numbers);
console.log(`配列の長さは ${result} です。`);

このコードを実行すると、getLength関数は配列の長さを正しく返します。

結果として「配列の長さは 4 です。」と表示されます。

このように、ジェネリクスを使用することで、型の柔軟性を保ちながらも型安全を確保することができます。

●注意点と対処法

TypeScriptを使って「値渡し」を行う際には、多くの便益がありますが、いくつかの注意点やそれに対する対処法が存在します。

初心者の方々がよく遭遇する問題を中心に、サンプルコードと共にその解決法を学びましょう。

○変数の変更と影響範囲

TypeScriptで関数やメソッドを使用する際、引数として渡される変数の値が関数内で変更されると、それが関数の外の変数に影響を及ぼすことがあります。

特に、オブジェクトや配列などの参照型のデータを扱う時、この問題は顕著になります。

このコードでは、オブジェクトを関数に渡し、関数内でそのオブジェクトのプロパティを変更する例を表しています。

この例では、関数外のオブジェクトも変更されてしまいます。

function modifyObject(obj: { value: number }) {
  // オブジェクトのプロパティを変更
  obj.value = 100;
}

const myObj = { value: 50 };
console.log(myObj.value); // 50と表示される

modifyObject(myObj);
console.log(myObj.value); // 100と表示される

このような場合、関数内で変数を変更する前に、その変数のコピーを作成し、コピーを変更することで、元の変数を変更しないようにすることが考えられます。

○不変性の重要性

上記の問題を防ぐためには、不変性(Immutable)を保つことが推奨されます。

不変性を保つことで、データの状態が予期しないタイミングで変わることを防ぐことができ、バグの発生を抑えることができます。

このコードでは、配列を関数に渡し、関数内で新しい配列を作成し、それを変更する例を表しています。

この例では、関数外の配列は変更されません。

function addToArray(arr: number[]): number[] {
  // 新しい配列を作成し、その配列に値を追加
  const newArr = [...arr];
  newArr.push(100);
  return newArr;
}

const myArray = [10, 20, 30];
console.log(myArray); // [10, 20, 30]と表示される

const updatedArray = addToArray(myArray);
console.log(updatedArray); // [10, 20, 30, 100]と表示される

こちらの例でわかるように、関数内で新しい配列やオブジェクトを作成することで、元のデータを不変に保ちながら処理を進めることが可能です。

不変性は、プログラム全体の安定性や予測可能性を高める要素として、多くの開発者に推奨されています。

●カスタマイズ方法

TypeScriptの魅力の一つは、多くのカスタマイズの選択肢が提供されている点にあります。

独自のコーディングスタイルやプロジェクトのニーズに合わせて、TypeScriptの動作を微調整することが可能です。

ここでは、TypeScriptのカスタマイズ方法について詳しく説明します。

○TypeScriptの設定でのカスタマイズ

TypeScriptのプロジェクトは、通常tsconfig.jsonという設定ファイルを使用してカスタマイズします。

このファイルは、コンパイラの動作やコードの品質に関する設定を管理する場所として利用されます。

例えば、次のサンプルコードは、tsconfig.jsonの一部を表しています。

{
  // コンパイルオプション
  "compilerOptions": {
    "target": "es6",  // ターゲットとなるECMAScriptのバージョン
    "module": "commonjs", // モジュールのフォーマット
    "strict": true,  // 厳格モードを有効にする
    // その他のオプション...
  },
  // コンパイル対象となるファイルや除外するファイルの指定
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

このコードでは、compilerOptions内でコンパイラの設定を行い、includeexcludeでコンパイル対象のファイルを指定しています。

この例では、ECMAScript 6をターゲットにしており、モジュールフォーマットはcommonjsを使用しています。

○コーディングスタイルとカスタマイズ

TypeScriptを使用する際、独自のコーディングスタイルを持つプロジェクトやチームがあるかと思います。

そこで、TypeScriptの機能や設定を活用して、独自のスタイルを保持しながら効率的にコードを記述する方法を考えることが重要です。

例えば、特定の型のみを使用したい、または特定の方法で型定義を行いたい場合、tslinteslintといったツールを活用して、コードの品質を維持することができます。

eslintの設定例を紹介します。

{
  "extends": ["plugin:@typescript-eslint/recommended"],
  "rules": {
    "@typescript-eslint/no-explicit-any": "error", // any型の使用を禁止
    "@typescript-eslint/explicit-function-return-type": "warn", // 関数の戻り値の型を明示的に指定する
    // その他のルール...
  }
}

この設定では、any型の使用を禁止して警告を出す設定や、関数の戻り値の型を明示的に指定することを求める設定が含まれています。

これにより、コーディングスタイルを統一し、コードの品質を維持することができます。

まとめ

TypeScriptを活用して「値渡し」を理解し、適切に利用する方法を学ぶことは、プログラムの品質や保守性を向上させるために非常に重要です。

この記事では、TypeScriptでの値渡しの10の具体的な方法を、初心者向けにわかりやすくサンプルコードを交えて解説しました。

これらの知識を基に、TypeScriptを使用したプログラミングのスキルを磨き、より質の高いコードを書く能力を高めていきましょう。