Vue.js の data() メソッドから覚える JavaScript のアロー関数とグループ化演算子

Pocket

Vue.js の data() メソッドから覚える JavaScript のアロー関数とグループ化演算子

今回は、JavaScript がチョットデキル風に見える書き方と、その構造を紹介します。

Vue.js における data() メソッドの複数の書き方・チョットデキル風の書き方

Vue.js でコンポーネントを定義する時、次のように data() メソッドを書くかと思います。

const myComponent = Vue.extend({
  name: 'my-component',
  data() {
    return {
      count: 0  // サンプルとして適当なデータを宣言・定義しています
    };
  },
  methods: { ... }
});

data() という書き方は ECMAScript 2015 以降で導入された糖衣構文で、以前からある「関数式」の構文だと次のように書きます。こちらも間違いではありません。

const myComponent = Vue.extend({
  name: 'my-component',
  data: function() {
    return {
      count: 0
    };
  },
  methods: { ... }
});

名前付きの関数式として書いたとしても、ここでは意味は変わりません。

const myComponent = Vue.extend({
  name: 'my-component',
  data: function data() {
    return {
      count: 0
    };
  },
  methods: { ... }
});

data メソッドはコンポーネント内で使用するデータの宣言・定義しか行わないので、基本的にはオブジェクト (連想配列) さえ return できればよく、data メソッド内で this を使う場面はないかと思います。そうすると、 アロー関数 を使って次のようにも書けます。

const myComponent = Vue.extend({
  name: 'my-component',
  data: () => {
    return {
      count: 0
    };
  },
  methods: { ... }
});

そして、後述するアロー関数の特徴を利用すると、以下のように JavaScript チョットデキル風の書き方 ができます。

const myComponent = Vue.extend({
  name: 'my-component',
  data: () => ({
    count: 0
  }),
  methods: { ... }
});

return キーワードがなくなり、count: 0 を記述している部分のインデントが一段階減らせました。

これは何をしているのか、これから説明します。

ちなみに:なぜ data() は関数である必要があるのか

Vue.js の data 部分は、なぜ直接連想配列を書かず、関数形式にする必要があるのでしょうか。

以下の公式リファレンスに記載がありましたが、純粋にオブジェクトを宣言してしまうと、同じコンポーネントを複数配置した時に、 オブジェクトの参照を共有してしまう のを避けるためです。

When defining a component, data must be declared as a function that returns the initial data object, because there will be many instances created using the same definition. If we use a plain object for data, that same object will be shared by reference across all instances created! By providing a data function, every time a new instance is created we can call it to return a fresh copy of the initial data.

コンポーネントの data オプションは関数でなければなりません 。各インスタンスが返されるデータオブジェクトの独立したコピーを保持できるためです:

Vue にこのルールがない場合、ボタンを1つクリックすると、以下のようにすべての他のインスタンスのデータに影響します:

ちょっと面倒臭い気もしますが、関数で書かないといけない理由は分かりました。

話を戻して、これをちょっと楽に書ける、チョットデキル風の書き方の詳細を見ていきましょう。

アロー関数の特徴:丸括弧や return を省略できるパターンがある

アロー関数は ES2015 以降で導入された構文で、this の扱いが異なるなど特徴に違いがありますが、基本的には function() { } と書いていた部分を置き換えられるような構文です。

アロー関数は引数と戻り値の表現方法に独特なところがいくつかあります。

例えば、 引数を1つだけ取るアロー関数 を書く場合は、丸括弧 (パーレン) を書かなくても良かったりします (以下のコード例の example2 部分)。

const example1 = () => {
  console.log('引数が 0 個の場合は丸括弧を書きます');
};

const example2 = value => {
  console.log('引数が 1 個だけの場合、丸括弧を省略できます', value);
};

const example3 = (value) => {
  console.log('引数が 1 個だけの場合、丸括弧を書いても結果は変わりません', value);
};

const example4 = (value1, value2) => {
  console.log('引数が 2 個以上の場合や、残余引数 (...values のような書き方) の場合は丸括弧が必須です', value1, value2);
};

また、処理部分が1行で済むような内容の場合、return キーワードを省略できます。

// 2つの引数を足して `return` します
const example5 = (value1, value2) => {
  return value1 + value2;
};

// return を省略できます。結果は example5 と全く一緒です
const example6 = (value1, value2) => value1 + value2;

example5(1, 2);  // → 3
example6(1, 2);  // → 3

他にも細かな仕様や面白いイディオムもあるのですが、一旦はここまでとします。

アロー関数で連想配列を返すには:グループ化演算子

さて、アロー関数を使うと return キーワードが省略できることが分かりました。では、単純な連想配列を返すアロー関数を書くにはどうしたら良いでしょうか。

// 単純に 1 という数値を返すアロー関数だと、以下のように書けます
const returnsNumber = () => 1;
const myNumber = returnsNumber();  // → 1

// 連想配列を返そうとすると…
const badExample = () => {
  count: 0,
  description: 'このコードは動きません'
};

オブジェクトを返そうと思って、アロー関数の矢印 => の直後にブレース {} 記号を書いてしまうと、これは構文エラーとなります。なぜなら、複数行のアロー関数を書く際の構文と区別が付かないからです。

// 最初のコード例と見比べてみましょう
const example1 = () => {
  // ここには 文 (Statements) が書けます
  console.log('引数が 0 個の場合は丸括弧を書きます');
};

そこで、 グループ化演算子 (Grouping Operator) を使います。これは、丸括弧で囲んだ内部の式が優先的に評価され、グループ化されるというものです。

const goodExample = () => ({
  count: 0,
  description: 'このコードなら動きます'
});

「グループ化演算子」自体は、算術演算や即時実行関数で既に使っている方もいるかもしれません。こんな名前が付いているとは、自分も最近まで知りませんでした。

  • 先に 1 + 2 を計算してから * 3 としたい場合
const calc = (1 + 2) * 3;
// → 9
  • 即時実行関数
(function() {
  console.log('この function() {} を囲んでいる手前の丸括弧 () が「グループ化演算子」です');
  console.log('その後ろにある丸括弧 () は、関数を実行するための丸括弧です');
})();

(() => {
  console.log('グループ化演算子でグループ化するところにアロー関数を使っても意味は同じです');
})();

const myFunc1 = function() {
  console.log('即時実行関数は、無名関数をその場で宣言して実行する構文ですが、');
  console.log('分かりやすく書き換えてみると次のようになります');
};
myFunc1();
(myFunc1)();  // ← グループ化演算子で囲んでも意味は同じです

((value1, value2) => {
  console.log('ちなみに、即時実行関数は引数を取ることもできます');
  console.log('value1 には「AAA」が渡されます → ', value1);
  console.log('value2 には「BBB」が渡されます → ', value2);
})('AAA', 'BBB');  // ← 関数の実行時に引数を2つ渡しています

const myFunc2 = (value1, value2) => {
  console.log('分かりやすく書き換えてみると、こういう関係です', value1, value2);
};
myFunc2('AAA', 'BBB');
(myFunc2)('AAA', 'BBB');

アロー関数においては、グループ化演算子を組み合わせることで、「連想配列を return している」と認識させられるわけです。

ちなみに、通常の関数式では、関数の文を示すためにブレース {} が必須であり、return キーワードを省略できる仕組みがないので、次のような書き方は構文エラーになります。

const myComponent = Vue.extend({
  name: 'bad-example-component',
  // `data() {}` は `data: function() {}` と同義です
  data() ({  // ← グループ化演算子のつもりで丸括弧を書いても構文エラーになります
    count: 0
  }),
  methods: { ... }
});

// 以下も全く同じで構文エラーです
const myComponent = Vue.extend({
  name: 'bad-example-component',
  data: function() ({
    count: 0
  }),
  methods: { ... }
});

アロー関数とグループ化演算子の組み合わせが秘訣

それでは最後にもう一度、Vue.js の data メソッド部分でインデントが減らせる、チョットデキル風の書き方をもう一度見てみます。

const myComponent = Vue.extend({
  name: 'my-component',
  data: () => ({
    count: 0
  }),
  methods: { ... }
});

return キーワードが書ける構造というのは、return 以前に何らか任意の処理を書き加えられてしまうような、隙を生む恐れもあります。

const myComponent = Vue.extend({
  name: 'my-component',
  data: () => {
    // あまり Vue.js に詳しくない人が、ここに副作用のある処理を書いてしまうかも…?!
    var badExample = axios.get('/api/hoge');  // (わざと間違ったコードを書いています)
    
    return {
      count: 0,
      badProperty: badExample + 10
    };
  },
  methods: { ... }
});

アロー関数とグループ化演算子を使うと、副次的に 「ここはオブジェクトを返すだけ、余計な処理は挟ませないぞ」 という表明にも受け取れるようになります。

const myComponent = Vue.extend({
  name: 'my-component',
  data: () => ({
    // ここに任意の処理を書こうとしても、構文エラーになるので抑止効果が望めます
    count: 0
  }),
  methods: { ... }
});

(無理やりやろうと思えば、連想配列の値部分で即時実行関数を書けば「処理」を挟むことはできますが…笑)

今回は Vue.js の data() メソッド部分で活用する方法を紹介しましたが、Array.prototype.map() などでアロー関数を使う際にも、グループ化演算子を使うとよりシンプルに実装できる場面があるかと思います。

const users = [
  { name: 'Kevin' },
  { name: 'Kake' },
  { name: 'Yama' }
];

const addedTeamName = users.map(user => ({ name: user.name, teamName: 'KER' }));

はじめのうちは for ループを使って書いた方が可読性が高いように感じるかもしれませんが、一つのことだけを行う関数を小さく作っておくと、汎用性が高く副作用を絞れて、後で改修する際にも適切な場所にコード追加ができるように、区別が付けられるようになると思います。

後から副作用を持ち込ませない手法という意味でも、こうした演算子を活用できるようになっておくと良いでしょう。

Pocket

コメントを残す

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