はじめに
React(JavaScriptライブラリ)を勉強する中で、分割代入を目にすることが頻繁にあります。
例えばprops
の受け取りや、useState
の戻り値は分割代入を使用することが一般的です。
しかし、その分割代入についてよく書く割に深いところまで理解できていないと感じたため、いま一度分割代入とはどのようなものかについて確認して、マスターしてきます。
まず初めに分割代入の基本的な構文について確認し、その後応用的な内容について触れていきます。
分割代入とは
配列や、オブジェクトなどの中身をいっぺんに変数に受け取ることが出来る代入方法のことを分割代入といいます。
配列の分割代入
配列の値を一つずつコンソールに出力したいとき、for文を使わずに一つずつ変数に格納すると、以下のようになります。
const FRUITS_ARRAY = ['apple', 'banana', 'cherry'];
const ap = FRUITS_ARRAY[0];
const bn = FRUITS_ARRAY[1];
const cr = FRUITS_ARRAY[2];
console.log(ap, bn, cr); // 「apple banana cherry」と出力される。
これを分割代入を使用して書き直してみます。
const FRUITS_ARRAY = ['apple', 'banana', 'cherry'];
const [ap, bn, cr] = FRUITS_ARRAY;
console.log(ap, bn, cr); // 「apple banana cherry」と出力される。
ポイント
ポイントは変数の宣言時、配列を定義するときと同じように[]
(角括弧)で囲むことです。
いちいちインデックスを指定してそれぞれの変数を定義する必要がないため、すっきりしているのがわかります。
要素が一致しない場合
では、配列の要素数と、宣言した変数の数が一致しない場合、どうなるのでしょうか。
const FRUITS_ARRAY = ['apple', 'banana', 'cherry'];
// 要素数より変数が少ない
const [ap1, bn1] = FRUITS_ARRAY;
console.log(ap1, bn1); // 「apple banana」と出力される。
// 要素数より変数が多い
const [ap2,bn2,cr,d] = FRUITS_ARRAY;
console.log(ap2,bn2,cr,d); // 「apple banana cherry undefined」と出力される
実は、要素数と変数の数が一致しなくてもエラーにはなりません。
配列の要素は、はじめから順番に変数へ代入されます。
変数が足りない場合にはそこで代入は終わりです。
変数が多い場合には、代入できるものがないので、未定義としてundefined
となります。
オブジェクトの分割代入
オブジェクトの分割代入についても確認します。
オブジェクトのプロパティを1つずつコンソールに出力したい場合は、以下のようになります。
const PROFILE = { name: 'John Doe', age: 24, country: 'America' };
const nm = PROFILE.name;
const ag = PROFILE.age;
const ct = PROFILE.country;
console.log(nm, ag, ct); //「John Doe 24 Ammerica」と出力される
1つずつプロパティにアクセスして、値を取り出しています。
これを分割代入を使って書き直してみます。
const PROFILE = { name: 'John Doe', age: 24, country: 'America' };
const { name, age, country } = PROFILE;
console.log(name, age, country); //「John Doe 24 Ammerica」と出力される
ポイント
ポイントは2つあります。
まず、オブジェクトを定義するときと同じように{}
(波括弧)で囲みます。
次に、オブジェクト内に定義されているプロパティ名と変数名が一致するようにします。
配列の場合、要素を順に代入するので変数名は自由でした。
オブジェクトの場合は、どのプロパティを取得するか指定するために、プロパティ名と変数名を一致させる必要があります。
こちらも、1つずつ定義するよりもすっきりしています。
要素が一致しない場合
では、要素数が一致しなかったり、プロパティ名が一致しなかったりするとどうなるでしょうか。
const PROFILE1 = { name1: 'John Doe', age1: 24, country1: 'America' };
const PROFILE2 = { name2: 'John Doe', age2: 24, country2: 'America' };
// 要素数が少ない
const { name1 } = PROFILE1;
console.log(name1); // 「John Doe」と出力される
// プロパティ名が一致しない
const { nm } = PROFILE2;
console.log(nm); // 「undefined」と出力される
// 順番が違う
const { country2, name2, age2 } = PROFILE2;
console.log(country2, name2, age2); // 「America John Doe 24」と出力される
要素数が少ない、または変数の定義順がオブジェクトの定義順と異なっていても問題はありません。
プロパティ名が一致する変数に対して、値が代入されます。
存在しないプロパティ名を指定してもエラーにはなりません。未定義のためundefined
となります。
分割代入を使いこなす
分割の代入の概要については理解できたかと思います。
ここからは分割代入をさらに使いこなすための記法を確認します。
デフォルト値を設定する
配列、オブジェクトともに、値が渡されなかった場合のデフォルト値を設定することができます。
配列のデフォルト値
const FRUITS_ARRAY = ['appl', 'banana'];
// 初期値設定なし、要素数不一致
const [ap, bn, cr] = FRUITS_ARRAY;
console.log(ap, bn, cr); // 「apple, banana, undefined」と出力される
// 初期値設定あり、要素数不一致
const [a, b, c = 'cherry'] = FRUITS_ARRAY;
console.log(a, b, c); // 「apple, banana, cherry」と出力される
// 初期値設定あり、要素数一致
const NUM_ARRAY = [100, 200, 300];
const [on, tw, th = 400] = NUM_ARRAY;
console.log(on, tw, th); // 「100 200 300」と出力される
先ほど確認したように、配列の要素数より多い変数を定義している場合、値が格納されない変数にはundefined
が代入されます。(1つめのconsole.log)
undefined
としたくない場合、変数名 = 初期値
のように定義することで、デフォルト値を割り当てることができます。(2つめのconsole.log)
未定義のときのみデフォルト値が設定されるため、値が定義済の場合にはデフォルト値は無視されます。(3つめのconsole.log)
なお、あくまでも未定義(undefined)のときのみであるため、以下の例のように、nullや0、空文字のときにもデフォルト値は無視されます。
const ARRAY = ['', 0, null, undefined];
const [w = 'first', x = 'second', y = 'third', z = 'fourth'] = ARRAY;
console.log(w, x, y, z); // 「 0 null forth」と出力される
オブジェクトのデフォルト値
オブジェクトのデフォルト値も定義方法は同じです。
const PROFILE = { name: 'John Doe', age: 24 };
const { name, age, country = 'America' } = PROFILE;
console.log(name, age, country); // 「John Doe 24 America」と出力される
PROFILE
オブジェクトには存在しないcountry
を変数として指定していますが、デフォルト値を設定しているため、undefined
にはなりません。
デフォルト値の使い所
上記だけみると有用性がわからないかもしれません。
関数で使用する場合を考えてみます。
function printProfile({ name = 'John Doe', age = 24, country = 'America' }) {
console.log(name, age, country);
}
const PROFILE1 = { name: 'JS Taro', age: 30, country: 'Japan' };
printProfile(PROFILE1); // 「JS Taro 30 Japan」と出力される
const PROFILE2 = { age: 42, country: 'China' };
printProfile(PROFILE2); // 「John Doe 42 China」と出力される
const PROFILE3 = {};
printProfile({}); // 「John Doe 24 America」と出力される
printProfile
関数の引数に分割代入の記法を使用しています。
また、それぞれのプロパティのデフォルト値を設定しています。
PROFILE1
の出力結果は設定している中身のとおりです。
PROFILE2
はname
プロパティを持ちませんが、デフォルト値が出力されます。
PROFILE3
は空のオブジェクトですが、すべてデフォルト値が出力されます。
このように、デフォルト値を使うことで、万が一プロパティが未設定の場合でもおかしな挙動とならないようにしておくことができます。
これはオブジェクトに限らず、配列でも同じことがいえます。
残りをまとめて受け取る
分割代入の際、配列やオブジェクトの数が多くなってくると、1つずつ指定するのが大変になってきます。
そこで使えるのが、残余引数(rest parameter)というものです。
const ARRAY = ['apple', 'banana', 'cherry', 'peach'];
// 配列の残余引数
const [ap, ...rest] = ARRAY;
console.log(ap); // 「apple」と出力される
console.log(rest); // 「['banana', 'cherry', 'peach']」と出力される
const PROFILE = { name: 'John Doe', age: 24, country: 'America', gender: 'male' };
// オブジェクトの残余引数
const { name, ...prof } = PROFILE;
console.log(name); // 「John Doe」と出力される
console.log(prof); // 「{age: 24, country: 'America', gender: 'male'}」 と出力される
...変数名
とすることで、指定していない残りの要素をまとめて配列、またはオブジェクトとして受け取ることができます。
変数宣言時に...
を付ける必要がありますが、使用する際は不要です。
また、残余引数は一番最後に記載する必要があります。
例えば、以下のコードはエラーになります。
const ARRAY = ['apple', 'banana', 'cherry', 'peach'];
const [ap, ...rest, pc] = ARRAY;
残ったものをまとめて受け取る書き方なので、残余引数が最後の要素でないと、どこまでを取得すればよいか特定できなくなるためです。
別名を割り当てる
これはオブジェクトにのみ適用できます。
オブジェクトの中身を分割代入で受け取るとき、変数名は、オブジェクトのプロパティと一致させる必要がありました。
しかし、どうしても変えたければ別名を割り当てることができます。
const PROFILE = { name: 'John Doe', age: 24, country: 'America' };
const { name: nm, age: ag, country: cn } = PROFILE;
console.log(nm, ag, cn); // 「John Doe 24 America」と出力される
console.log(country); // countryが未定義としてエラーになる
プロパティ名:変数名
とすることで、任意の変数名でオブジェクトの値を受け取ることができます。
オブジェクトの各プロパティを変数名に割り当てているため、オブジェクトのプロパティ名を指定しても、未定義としてエラーになります。
ただ、この書き方だと元のオブジェクトのプロパティとのつながりがわからなくなってしまうので、個人的にはあっまり使う機会はないのかなと思っています。
分割代入の注意点
分割代入は、値そのものではなく、値への参照をコピーします。
(参照とは住所のようなものです。詳しい説明はここでは割愛します。)
そのため、入れ子になった配列やオブジェクトを使用する場合は注意が必要です。
const ARRAY = ['apple', 'banana', ['cherry', 'peach']];
const [ap, bn, child] = ARRAY;
// 要素追加前
console.log(child); // 「['cherry', 'peach']」と出力される
console.log(ARRAY[2]); // 「['cherry', 'peach']」と出力される
// 要素を追加
child.push('orange');
// 要素追加後
console.log(child); // 「['cherry', 'peach', 'orange']」と出力される
console.log(ARRAY[2]); // 「['cherry', 'peach', 'orange']」と出力される
配列の3番目(インデックスは2)の要素をchild
という変数に代入しました。
child
に対して要素を追加すると、何も変化を加えていないはずのARRAY[2]
にも要素が追加されています。
これはchild
にはARRAY[2]
の参照がコピーされているためです。
child
とARRAY[2]
ともに同じ参照を見ているので、child
の要素を変更した場合、ARRAY
に対して変更を加えていなくても、影響を受けるということになります。
この問題に対処するためにはスプレッド演算子 というものを使用する必要がありますが、これはまた別の機会に解説しようと思います。
まとめ
少し長くなりましたが、分割代入の基本と応用的な書き方を確認してきました。
配列であれば[]
、オブジェクトであれば{}
で変数を囲むことで、配列、またはオブジェクトの中身をまとめて受け取ることができます。
いちいちインデックスやプロパティを指定せずに書くことが出来るため、スッキリしたコードになります。
また、関数で引数をオブジェクトや配列で受け取る場合に重宝しそうです。