追加と削除が繰り返される配列要素のオブジェクトに一意のid番号を振る

配列要素のオブジェクトに一意のid番号を振りたい場合があります。たとえば、Vue.jsでデータを動的にページの要素として差し込む場合([`v-for`ディレクティブ](https://jp.vuejs.org/v2/guide/list.html#v-for-%E3%81%A7%E9%85%8D%E5%88%97%E3%81%AB%E8%A6%81%E7%B4%A0%E3%82%92%E3%83%9E%E3%83%83%E3%83%94%E3%83%B3%E3%82%B0%E3%81%99%E3%82%8B))、データのオブジェクトには一意のプロパティが求められます(「[キー付きv-for](https://jp.vuejs.org/v2/style-guide/#キー付き-v-for-必須)」参照)。データの追加や削除が繰り返されるとき、新たなデータの一意の整数をどのようにして求めるかが課題です。

# 数値をカウントアップする
もっとも簡単なのは、新たに加えるデータの整数をはじめに決めたら、あとはひたすらカウントアップすることです。

```js
// はじめの数値
let nextId = data.length;

// データを加えるとき
data.push({
	id: nextId++,
	// 他のプロパティ
});
```

これでも、動作にはことさら問題はないでしょう。ただ、データの追加と削除を繰り返したとき、データ総数はさほどなくても、最後のデータのidはどんどん大きくなります。idを表示した場合は、見栄えがよくないかもしれません。

# 使われている最大値を調べる
今あるデータの中で使われているidの最大値を調べれば、値がやみくもに大きくなることは避けられます。引数に受け取ったデータの中からidに使われている最大の値を調べて、1加えた数値を返すのがつぎの関数です。

```js
function getNewId(data) {
	const ids = data.map((element) => element.id);
	return Math.max(...ids) + 1;
}
```

[`Array.prototype.map()`メソッド](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map)は、引数の関数に要素を渡し、その戻り値からなる新たな配列をつくって返します。得られるのは、id番号の配列です。そこで、`Math.max()`メソッドにより最大値を取り出したうえ、1加えて返しています。なお、ECMAScript 2015の[スプレッド構文`...`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax)は、配列の要素をカンマ区切りの引数として渡します(「JavaScript: ECMAScript 2015のスプレッド構文・残余引数・分割代入を使ってみる」の「[スプレッド構文](https://qiita.com/FumioNonaka/items/20e96ca9ede2ae157bcc#スプレッド構文)」参照)。

これで、id番号がひたすら大きくなることはなくなります。つぎのサンプル001は、この関数と同じ処理で新たに加えるデータのidを定めました(「[Vue.js + ES6: SVGでレーダーチャートを操作する](http://www.fumiononaka.com/Business/html5/FN1803005.html)」参照)。実用としては、これで問題はないでしょう。

#### サンプル001■Vue.js + ES6: Radar chart with SVG controlled dynamically - final
<p class="codepen" data-height="480" data-theme-id="default" data-default-tab="js,result" data-user="FumioNonaka" data-slug-hash="yLLdJJp" style="height: 480px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Vue.js + ES6: Radar chart with SVG controlled dynamically - final">
  <span>See the Pen <a href="https://codepen.io/FumioNonaka/pen/yLLdJJp">
  Vue.js + ES6: Radar chart with SVG controlled dynamically - final</a> by Fumio Nonaka (<a href="https://codepen.io/FumioNonaka">@FumioNonaka</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

#空き番号を探して使う
前項の「使われている最大値を調べる」考え方ですと、小さい番号に抜けが生じてしまうのは仕方ありません。けれど、その穴も埋める処理を定めたのが、以下の関数です。

まず、使われたidを、[`Array.prototype.reduce()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce)メソッドで配列としてまとめます。インデックスはidに読み替え、値が`true`ならすでに使われているということです。つぎに、ECMAScript 2015の[`Array.prototype.findIndex()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) メソッドにより、値が`true`でない最初のインデックスを探します。すべてのidが`true`で埋まっていたら、戻り値は-1です。その場合は、`Array.length`プロパティの値が、つぎのidとなります。

```js
function getNewId(data) {
	const usedIds = data.reduce((accumulator, element) => {
		accumulator[element.id] = true;
		return accumulator;
	}, []);
	const nextid = usedIds.findIndex((exists) => !exists);
	return nextid < 0 ? usedIds.length : nextid;
}
```

前掲サンプル001の作例を、ご参考までにうえの関数(メソッド)と同じ処理に書き替えて掲げます(サンプル002)。

#### サンプル002■ Vue.js + ES6: Radar chart with SVG controlled dynamically with unique id
<p class="codepen" data-height="480" data-theme-id="default" data-default-tab="js,result" data-user="FumioNonaka" data-slug-hash="gOONMdg" style="height: 480px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Vue.js + ES6: Radar chart with SVG controlled dynamically with unique id">
  <span>See the Pen <a href="https://codepen.io/FumioNonaka/pen/gOONMdg">
  Vue.js + ES6: Radar chart with SVG controlled dynamically with unique id</a> by Fumio Nonaka (<a href="https://codepen.io/FumioNonaka">@FumioNonaka</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>


# Vue.jsのkey属性に配列インデックスは使わない
前項の作例では、Vue.jsの`v-for`ディレクティブで、[`key`属性](https://jp.vuejs.org/v2/api/#key)に一意の番号を与えました。もっとも、`v-for`ディレクティブは、データの配列インデックスも取り出せます。一意でありさえすればよいのでしたら、この値を使えばよさそうです。

```html
<axis-label
	v-for="(stat, index) in stats"
	:stat="stat"
	:index="index"

	:key="stat.id">
</axis-label>
```

けれども、配列インデックスは、はじめの方の要素が取り除かれると、そのあとすべての要素の番号が振り直されます。どの要素が変更されたのか、`key`属性によって絞り込もうとしているとき、他の項目の値まで変わってしまったら変更する範囲が無駄に広がります。それだけでなく、動作に問題が生じることもありえます(「[Vue.js: v-forで項目インデックスをkey属性にしていいのか](https://qiita.com/FumioNonaka/items/d1d9c9335116426a8316)」参照)。

したがって、本稿で解説したように、追加・削除するデータ以外の値は変えないようにした方がよいのです。

[追記: 2019年11月26日] 「空き番号を探して使う」項のコードで用いるメソッドを、`Array.prototype.forEach()`から`Array.prototype.reduce()`に変更。本文の一部を加筆・補正し、サンプルの掲載はjsdo.itからCodePenに改めた。
[追記: 2018年04月21日]「Vue.jsのkey属性に配列インデックスは使わない」の項を追加。