JavaScript(ES6)のスプレッド構文とレスト構文と分割代入が意外と多機能だった


背景

  • ES6のスプレッド構文(Spread Syntax)とレスト構文(Rest Parameters)について、Rubyの可変長引数みたいなものだと思っていたのだけど、調べてみたら思ったより便利だったことを知ったのでまとめてみた。
  • レスト構文を調べてたら関連して分割代入(Destructuring Assignment)にも言及せざるを得なくなったのでついでに追加した。

スプレッド構文(Spread Syntax)

Array編

関数の引数に使うパターン

  • 引数の定義に使うと、実行時の複数の入力を1つの配列として受け取ることができる。
const func = (...args) => {
  console.log(args);
}

func(1, 2, 3); // => [1, 2, 3]
  • 実行時に配列をスプレッド構文で展開して引数に渡すと、配列内の各要素を関数内で別々の引数として受け取ることができる。
const func = (a, b, c) => {
  console.log(a, b, c);
}

const array = [1, 2, 3];
func(...array); // => 1, 2, 3;

配列の定義に使うパターン

  • 配列を定義する際に別の配列の要素をスプレッド構文で展開すると、各要素を新しい配列の要素として渡すことができる。
const array = [1, 2, 3];
const newArray = [...array, 4, 5, 6];
console.log(newArray); // => [1, 2, 3, 4, 5, 6]
  • 展開する変数が undefinednull だった場合は例外エラーが発生する。
const hoge = undefined;
const fuga = null;

const array1 = [...hoge];
// => Uncaught TypeError: undefined is not iterable

const array2 = [...fuga];
// => Uncaught TypeError: null is not iterable

余談

  • 文字列も文字の配列のような形で展開することができる。
const func = (...args) {
  console.log(args);
}

func(...'abc'); // => ['a', 'b', 'c']

Object編

関数の引数に使うパターン

  • 引数のオブジェクトの中で特定のプロパティだけを抜き出すことができる。
const callName = ({ name }) => {
  console.log(name);
};

const user = {
  name: 'Takuya';
};

callName(user); // 'Takuya'
  • さらに深い階層を展開することもできるが、ここまで書くとちょっと可読性が下がる気がしている。
const callName = ({ user: { name } }) => {
  console.log(name);
};

const params = {
  user: {
    name: 'Katsuya',
  };
};

callName(params); // => 'Katsuya'

オブジェクトの定義に使うパターン

  • オブジェクトをプロパティと値の組に分解してマージできる。
  • プロパティ名がかぶった場合は後から書いた方が優先される。
const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { a: 10, d: 4, e: 5 };

const clonedObj = { ...obj1 };
console.log(clonedObj); // => { a: 1, b: 2, c: 3 }

const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // => { a: 10, b: 2, c: 3, d: 4, e: 5 }
  • React/ReduxのReducer内でオブジェクトの中身を書き換える際とかによく見かける。
const updateName = (state, user) => {
  return { ...state, user };
}

const initialState = { id: 1, name: 'Nobuto' };
const newName = 'Shintaro';
const newState = updateName(initialState, newName);

console.log(newState); // => { id: 1, name: 'Shintaro' }
  • ちなみに展開する変数が undefinednull だった場合は何も起きない。
const hoge = undefined;
const fuga = null;

const obj1 = { ...hoge }; // => {}
const obj2 = { ...fuga }; // => {}

レスト構文(Rest Parameters)

Array編

関数の引数に使うパターン

  • 指定した引数より多くの引数が実行時に渡された場合、残りの引数をまとめて1つの配列として受け取れる。
  • この辺はRubyの可変長引数と一緒。
  • 残りの引数さえ使っていれば、指定した引数を使わなくても eslint や tslint で 'xxx' is declared but its value is never read. のようなエラーは出ない。
const func = (a, b, ...other) => {
  console.log(other);
};

func(1, 2, 3, 4, 5); // => [3, 4, 5]

配列の展開に使うパターン

  • 分割代入との組み合わせで、配列から指定した数の要素だけ抜き出して、残りを1つの配列として受け取ることができる。
const array = [1, 2, 3, 4, 5];
const [a, b, c] = array;

console.log(a); // => 1
console.log(b); // => 2
console.log(c); // => [3, 4, 5]

Object編

関数の引数に使うパターン

  • 引数の中で特定のプロパティだけを抜き出し、残ったプロパティをまとめて1つのオブジェクトとして受け取ることができる。
  • これも残りの引数さえ使っていれば、指定した引数を使わなくても eslint や tslint で 'xxx' is declared but its value is never read. のようなエラーは出ない。
const func = ({ name, ...otherProps }) => {
  console.log(otherProps);
}

const user = { name: 'Akira', age: 34, instrument: 'Guitar' };
func(user); // => { age: 34, instrument: 'Guitar' }

オブジェクトの展開に使うパターン

  • オブジェクトの中で特定のプロパティだけを抜き出し、残ったプロパティをまとめて1つのオブジェクトとして受け取ることができる。
const user = { name: 'Akira', age: 34, instrument: 'Guitar' };
const { name, ...rest } = user;

console.log(name); // => 'Akira'
console.log(rest); // => { age: 34, instrument: 'Guitar' }

分割代入(Destructuring Assignment)

Array編

  • 配列の要素を抜き出して別の変数に展開したい時、一括で代入することができる。
const [hoge, fuga] = [1, 2];
console.log(hoge); => 1
console.log(fuga); => 2

const [foo, bar, baz] = [3, 4];
console.log(baz); // => undefined
  • 配列の要素数が足りない場合は undefined になる
const [foo, bar, baz] = [3, 4];
console.log(baz); // => undefined
  • 最初の方の要素が要らない場合はカンマで無視できる
const array = [1, 2, 3, 4, 5];
const [,second] = array;
const [,,third] = array;

console.log(second); // => 2
console.log(third); // => 3
  • 指定された要素が undefined だった場合のデフォルト値を指定できる
  • 指定された要素が null だった場合は null が代入される
const [a = 1] = [];
console.log(a); // => 1

const [b = 2] = [undefined];
console.log(b); // => 2

const [c = 3] = [null];
console.log(c); // => null

余談

  • 文字列も文字の配列のような形で展開することができる。
const [a, b, c] = 'JS';
console.log(a); // => 'J'
console.log(b); // => 'S'
console.log(c); // => undefined

const [d, e, ...f] = 'JavaScript';
console.log(d); // => 'J'
console.log(e); // => 'a'
console.log(f); // => ['v', 'a', 'S', 'c', 'r', 'i', 'p', 't']

Object編

  • オブジェクトのプロパティ名が一致する変数に一括で代入できる。
const user = { age: 28 };
const { age } = user;
console.log(age); // => 28
  • 変数名を変更することもできる。
const user = { age: 28 };
const { age: newAge } = user;
console.log(newAge); // => 28
console.log(age); // => Uncaught ReferenceError: age is not defined
  • デフォルト値を指定することもできる。
const user = { name: 'Riri', gender: 'female' };
const { instrument = 'none' } = user;

console.log(instrument); // => 'none'

そろそろ飽きてきたのでこの辺で。ドキュメントを読むと想像以上に色んな書き方があって面白い。

参考