オブジェクト

【TypeScriptで学ぶ】オブジェクトの分割代入

unit-code@wp-admin

こんにちは、めんだこです!

今回は、オブジェクトの分割代入について解説していきます。

今回の構成としては、まずはJavaScriptで基本的な構文を復習し、TypeScriptではどのように使われているのか、について見ていきます。

TypeScriptを触っていると、どこまでがJavaScriptの用法で、どこからがTypeScriptの用法なのか、混乱している方も少なくないと思います。

今回は、そんな方に向けて頭の中の引き出し整理のための記事になればと思っています!
どうぞよろしくお願いします!

1

JavaScriptにおける構文

まずは、JavaScriptにおける分割代入の構文を見いきます。

ここで使われている構文は、すべてTypeScriptでも使う事ができますので、しっかりと復習していきましょう!

1.1 基本

まずは、基本となる分割代入の構文を確認します。
次のコードをご覧ください。

const obj = {
	a: "Hello",
	b: "World",
	z: "rest",
};

// 分割代入
const { a, b } = obj;

console.log(a, b); // output: Hello World

この場合、分割代入部分は次のコードと等価です。

const a = obj.a;
const b = obj.b;

分割代入の構文を使うことで、記述が1行に抑えられ、objの呼び出し回数も1回に削減できていることが分かります。

また、プロパティと同名の定数(変数)を宣言している時は、対応するプロパティの値がそれぞれ割り当てられます。

さらに、不要なプロパティはそのまま無視されます。
今回の場合だと、使われないプロパティ「z」は、objの形を変更せずに放っておくことができます。

1.2 ネスト

次に、ネストしているオブジェクトの分割代入を確認します。
次のコードをご覧ください。

const nest = {
	a: "Hello",
	b: {
		c: "Nest obj",
	},
};

// 分割代入
const {
	a,
	b: { c: d },
} = nest;

console.log(a, d); // output: Hello Nest obj

このように、ネストしたオブジェクトに対して、分割代入でその値を取得するときには、元のオブジェクトのパターンを書くことになります。

この例では2段階にネストしたオブジェクトを見ましたが、実用的には多くて3段階程度のネストで分割代入を使うこともあるでしょう。
そのような場合も同様に、さらにネストしたパターンを書くことで、値にアクセス可能です。

1.3 オリジナル変数名

これまで、元のプロパティと同名の変数名を宣言して分割代入を行なってきました。
この方法は、オブジェクトと変数の関係性が明確になりコードの可読性も高いため、優れた方法として積極的に活用すべきです。

しかし、あえて別の変数(定数)名をつける真っ当な理由がある場合は、オブジェクトのプロパティとは異なる変数名を割り当てることも可能です。

次のコードを見てみましょう。

const obj = { a: 1, b: 2 };

const numbers = [];

// 分割代入
// ()は即時実行関数式: この場合{}がブロックとして認識されるのを防ぎ、
// オブジェクトリテラルとして認識するための役割を果たす
({ a: numbers[0], b: numbers[1] } = obj);

console.log(numbers); // output: [ 1, 2 ]

分割代入の際に、プロパティの横に「:(変数名)」とすることで、それぞれのプロパティの値を任意の変数名に割り当てることができます。

今回の場合は、オブジェクトの数値をnumbersとして一つの定数に保存することで、値の意図を明確にしています。

このように、オブジェクトのプロパティ名を、そのまま引き継がないことでコードの意図が明確になるような場合などには、こちらの構文も検討してみましょう。

注意
TypeScriptでこの構文を使うときは、少し注意が必要です。
「:」を見た時に、反射的にTypeScriptの型注釈を連想してしまう場合があるためです。
よく見れば、別の変数名を割り当てているとわかるのですが、特に最初のうちは気をつけましょう。
私はいまだに引っ掛かります笑。

1.4 規定値

const { a: a1 = aDefault, b = bDefault } = obj;

このように、あらかじめ規定値を設定しておくことで、オブジェクトのプロパティを評価して「undefined」が渡された場合に、代わりに設定した規定値を割り当ててくれます。

次の例を見てみましょう。

const obj = {
	a: "Hello",
	b: "World",
};

const { a, b, z = "z規定値" } = obj;

console.log(a, b, z); // output: Hello World z規定値

元々objに存在しない「z」を勝手に宣言しています。
そのままだと、「z === undefined」なのですが、規定値を宣言することによって、これを解消しています。

この用法はTypeScriptのオプショナルプロパティと相性が良いので、よく使われます。
後ほど、詳しく解説します。

1.5 残余プロパティ

余ったプロパティをまとめて回収してくれる構文も用意されています。

const fruitsBasket = {
	banana: 1,
	apple: 3,
	orange: 2,
	grape: 2,
};

const { apple, ...rest } = fruitsBasket;

console.log("りんご:", apple); // output: りんご: 3
console.log("あまり:", rest);  // output: あまり: { banana: 1, orange: 2, grape: 2 }

restをスプレッド演算子( ... )と組み合わせることで、apple以外の果物を一つのオブジェクトにまとめてくれます。

コラム 関数の引数としての利用

オブジェクトの分割代入は、ただ変数を宣言するだけでなく、関数の引数としてもよく利用されています。

例えば、次のようなコードがあったとします。

const wallet = {
	owner: "mendako",
	bill: {
		1000: 3,
		5000: 2,
		10000: 1,
	},
};

function walletSum(wallet) {
	return (
		wallet.bill[1000] * 1000 +
		wallet.bill[5000] * 5000 +
		wallet.bill[10000] * 10000
	);
}

console.log(walletSum(wallet)); // output: 23000

これは、次のコードと等価です。

const wallet = {
	owner: "mendako",
	bill: {
		1000: 3,
		5000: 2,
		10000: 1,
	},
};

// 引数部分が分割代入になっている
function walletSum({ bill }) {
	return (
		bill[1000] * 1000 +
		bill[5000] * 5000 +
		bill[10000] * 10000
	);
}

console.log(walletSum(wallet)); // output: 23000

こちらの方が、より簡潔に関数walletSum()を記述できています。
walletを評価する回数も、関数呼び出し時の1度のみとなっていますので、パフォーマンスにも優れたbetterな書き方といえます。

実際の現場でもよく使われますので、ぜひ覚えておきましょう。

2

TypeScriptにおける構文

ここからは、オブジェクトの分割代をTypeScriptに拡張させてみてみます。

基本的にはJavaScriptと構文は変わらないので、TypeScriptで使う際の注意点と、よく使われる用法について解説していきます。

2.1 注意点

TypeScriptのオブジェクトの分割代入には、一つ注意点があります。

それは、分割代入で宣言された変数・定数については、型注釈ができないということです。

TypeScriptで通常の変数を宣言をするときなどは、このように型を指定することができていました。
(参考:オブジェクト入門 2.2 型注釈による型の指定、2.3 型エイリアス(type)を用いた構造定義)

// 型注釈によるオブジェクトの宣言
type User = {
	name: string;
	age: number;
};

const charlie: User = {
	name: "charlie",
	age: 32,
};

しかし、分割代入の際には、この型注釈はできません。

// 分割代入
const { name, age } = charlie;

console.log(name, typeof name); // charlie string
console.log(age, typeof age); // 32 number

このような場合、それぞれの型は型推論により決定されます。

型注釈ができない理由として考えられるのは、(おそらくですが)上記「1.3 オリジナル変数名」を使用する際に「:」を使っているため、

  • 「:」がどちらの用法で使われているか、の判別にコストがかかり可読性が低下する。
  • TypeScriptのトランスパイラに、型注釈の「:」とオリジナル変数名の「:」の区別をさせるための、パフォーマンスコストが高い。

といった理由が考えられます。
実際のところは分からないので、もしご存知の方がいらっしゃいましたら、コメントにて教えてくださると嬉しいです。

2.2 規定値の設定とオプショナルプロパティ

こちらのコードを見てみましょう。

お財布の中身に応じて、お小遣いを支給する関数を作ってみました。

// 型宣言
type Bill = {
	1000?: number; // optional
	5000?: number; // optional
	10000?: number; // optional
};

type Wallet = {
	owner: string;
	bill: Bill;
};

// めんだこの財布を定義
const mendakoWallet: Wallet = {
	owner: "mendako",
	bill: {
		1000: 2,
	},
};

// お札がない場合、規定値の分だけ支給してあげる関数
function okozukai(wallet: Wallet): Wallet {
	const bill: Bill = {};
	({
		bill: {
			1000: bill[1000] = 4, // 1000円札を持っていなければ、4枚支給
			5000: bill[5000] = 1, // 5000円札を持っていなければ、1枚支給
			10000: bill[10000] = 1, // 10000円札を持っていなければ、1枚支給
		},
	} = wallet);

	return {
		owner: wallet.owner,
		bill,
	};
}

const newWallet = okozukai(mendakoWallet);

console.log(newWallet); // { owner: 'mendako', bill: { '1000': 2, '5000': 1, '10000': 1 } }

このように、TypeScriptではオプショナルプロパティが設定できるため、規定値を設定してあげることがとても便利に働くことがあります。

今回の場合、それぞれのお札の枚数が「undefined」となった時には、設定した規定値分のお小遣いをあげることを、分割代入によって実現しています。

3

例題

これまでの内容の復習も兼ねて、例題を作成してみました。

頭の体操として、お時間のある方はぜひ挑戦してみてください!

問題はJavaScriptでの出題です。
最後に出力される値を考えてみましょう。

例題1

const obj = {
	a: 100,
	b: {
		c: {
			d: 200,
		},
		e: 300,
	},
};

// 初期化
let a;
let b;
let c;
let d;
let e;

// 分割代入
({
	a,
	b: { c: d, e },
} = obj);

// 出力
console.log(a);
console.log(b);
console.log(c);
console.log(d);
console.log(e);
Q
解答

100
undefined
undefined
{ d: 200 }
300


{ d: 200 } なのが、少し意地悪でしたね。
200 として出力したい場合は、分割代入部分をこのように修正するとうまくいきます。

// 分割代入
const {
	a,
	b: { 
        c: { d }, 
        e 
    },
} = obj;

「c」の値はオブジェクトなので、このように記述する必要がありますね。

例題2

const { a = 2 } = { a: undefined };
const { b = 2 } = { b: null };

// 出力
console.log(a);
console.log(b);
Q
解答

2
null


規定値の設定が有効なのは、値が「undefined」の時に限ります。

よく混乱しますが、JavaScriptにおいて「null」と「undefined」は若干の違いがあります。

詳しく知りたい方は、こちらが分かりやすいですので、ぜひ一度はご覧になってください。

例題3

const { b = console.log("hello!") } = { b: undefined };
Q
解答

hello!


イレギュラーですが、規定値にはオブジェクトも設定できます。
JavaScriptにおいて、関数は呼び出し可能なオブジェクトですので、こういった関数呼び出しも可能です。

4

参考

こちらの記事の執筆にあたり、以下のWebサイト・書籍を参考・利用させていただきました。

ありがとうございました!

【Webサイト】

【書籍】

ご覧いただきありがとうございました。

より良い記事を提供するため、この記事に対するご指摘・ご感想等ございましたら随時コメントを募集しております。
お気軽に書き込みくださいませ。

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA


ABOUT ME
めんだこ
めんだこ
webエンジニア

バックエンドの技術を中心に、フロントエンドやインフラ周りの知識も記事にしています!
・プログラミングの学習法や知識
・エンジニアとして考えていること など
を共有して、より良いエンジニアライフを送るための情報を発信します!


新卒未経験でwebエンジニアとなり、翌年からフリーランスとして活動している、現在25歳。
未だにプログラミングが楽しくて仕方ないらしい。
記事URLをコピーしました