概要
JavaScriptのArrayを使う上で、Arrayにはどんな機能があって、どういう場面でどう便利に使えるのか、ぜひ知っておきたいです。
そうしないと、Arrayのさまざまな関数でできることって、結局for文で手続き的に書いていけば実装できてしまうので、ソースがfor文だらけになってしまうかもしれません。
弊社のマッチングアプリ『CoupLink』は、JavaScriptで書かれているのですが、ソース内でArrayの機能がそれぞれどれくらい、そしてどのように使われているのかまとめてみました。
- 添付しているアプリのスクリーンショットはダミーのものになります。
- 例で出しているサンプルのデータは、説明しやすいよう新たに定義したもので、実際の構造とは関係ありません。
- 例で出しているJavaScriptのコードも同様に、説明しやすい内容で新たに書いたもので、実装のソースとは関係ありません。
1位 filter (70回)
特に特殊な使い方はしておらず、配列から特定のものに絞りたい場合に使っています。
↑全スタンプのうち、選択しているタブのスタンプだけを表示します
// チャットで使える複数の種類のスタンプを全てフラットな配列で受け取っている
const stamps = [
{ id: 1, name: 'stamp1-1', group: 1 },
{ id: 2, name: 'stamp1-2', group: 1 },
// ...
{ id: 41, name: 'stamp2-1', group: 2 },
{ id: 42, name: 'stamp2-2', group: 2 },
// ...
{ id: 239, name: 'stamp6-39', group: 6 },
{ id: 240, name: 'stamp6-40', group: 6 }
]
// 選択した種類のスタンプのみにフィルタリングする
const selectedGroup = 1
const selectedStamps = stamps.filter(v => v.group === selectedGroup)
// [{ id: 1, name: 'stamp1-1', group: 1 }, { id: 2, name: 'stamp1-2', group: 1 }, ...]
また、配列からfalsyな値を取り除きたいときなどにも使っています。
const values = ['val1', null, 'val3', null]
users.filter(v => v) // ['val1', 'val3']
// または
users.filter(Boolean) // ['val1', 'val3']
2位 map (65回)
mapは体感でもかなり使っている感じがします。
(都道府県idのみの配列を、都道府県文字列でマッピング)
// 都道府県マスタ
const PREFECTURES = [
{ id: 1, name: '北海道' },
// ...
{ id: 47, name: '沖縄県' }
]
// 都道府県のidのみが格納された配列
const selectedPrefectureIds = [1, 2, 3]
// 上記のidの配列をマスタの内容でマッピング
const selectedPrefectures = selectedPrefectureIds.map(id => {
return PREFECTURES.find(p => p.id === id)
})
// [
// { id: 1, name: '北海道' },
// { id: 2, name: '青森県' },
// { id: 3, name: '岩手県' }
// ]
単純に深い階層にある値だけを取り出したり、
const prefectures = [
{ id: 1, name: '北海道' },
{ id: 47, name: '沖縄県' }
]
const prefectureNames = prefectures.map(v => v.name) // ['北海道', '沖縄県']
行ごとに何か関数を実行するような場合でも、返り値があるならmapを使うことが多いです。
const results = arr.map(v => execSmothing(v)) // [true, false]
3位 find (66回)
前提として、連続したデータは基本的にArrayで扱っています。
例えば複数のユーザーを変数で扱うとき、以下のようにidなどをキーとしてObjectに格納する、といったことはしません。
const users = {
1: { name: 'user1' },
2: { name: 'user2' },
3: { name: 'user3' }
}
const user2 = users[2] // { name: 'user2' }
以下のようなデータを扱います。
const users = [
{ id: 1, name: 'user1' },
{ id: 2, name: 'user2' },
{ id: 3, name: 'user3' }
]
このとき、特定行の中にある値を元にその行を取得したい場合にfindを使います。
const user2 = users.find(u => u.id === 2) // { id: 2, name: 'user2' }
使われ方としてはほとんどがこのケースでした。
4位 includes (62回)
includesはほぼif文でのORを短くする目的で使われていました。
if (response.statusCode === 401 || response.statusCode === 403) {
// Do something
}
↑を↓のように書きたい。
if ([401, 403].includes(response.statusCode)) {
// Do something
}
起点の配列側が定数となり、引数が検証される側の変数となります。
5位 forEach (34回)
主に行ごとに何か別の関数を実行したいときなどに使われています。
const params = new URLSearchParams()
userIds.forEach(id => params.append('user_ids[]', id))
もしforEachやfor文の利用回数が一番多い人は、例えば以下のように他のArray関数が使えるケースでもfor文を使ってしまっているかもしれません。
const users = [
{ id: 1, gender: 'male' },
{ id: 2, gender: 'female' },
{ id: 3, gender: 'male' }
]
// femaleだけの配列にしたい
const femaleUsers = []
users.forEach(user => {
if (user.gender === 'female') femaleUsers.push(user)
})
// filterが使えます
const femaleUsers = users.filter(user => user.gender === 'female')
5位 join (25回)
joinはいたって普通の使い方で、配列を文字列で列挙する際に使っています。
これ以外の使い方はあまり思いつきません。
const user1 = {
name: 'user1',
hobbies: [{ id: 1, name: '映画鑑賞' }, { id: 2, name: '音楽' }, { id: 3, name: 'グルメ' }]
}
user1.hobbies.map(h => h.name).join(', ') // 映画鑑賞, 音楽, グルメ
6位 some (14回)
一行でもcallbackでtrueを返せば、結果がtrueになる関数です。
そのままの使い方では、フォームのバリデーションなどで使っていました。
const arr = [
{ question: '質問1', answer: '回答1' },
{ question: '質問2', answer: '' },
{ question: '質問3', answer: '回答3' },
]
if (arr.some(v => v.answer === '')) alert('未入力項目があります')
他の使い方:
someはループ中にtrueが見つかれば、その時点でtrueを返し、それ以降の行のコールバックは実行されません。
この仕様を利用して、break文のような処理を書くことができます。
const arr = ['arg1', 'arg2', 'arg3']
arr.some(arg => {
const result = execSomething(arg)
return !result // 失敗したらbreak
})
// execSomething('arg2') で失敗したら execSomething('arg3') は実行されない
7位 reduce (5回)
一般的にはドキュメントにある例のように、各行を集計して1つの結果にまとめる、といった使いかと思われます。
const arr = [1, 5, 10, 20]
const sum = arr.reduce((prev, current) => prev + current) // 36
CoupLinkでは、第二引数の初期値を利用して新たなObjectやArrayを組み立てるためにたまに使われていました。
例えば、上のスクリーンショットの例では、フラットに格納されたチャットのメッセージの配列を、日付ごとにグルーピングした状態したいときに、reduceを以下のように使っています。
// メッセージの一覧がフラットに格納された配列を
const massages = [
{ date: '2019-12-01', message: 'm1' },
{ date: '2019-12-01', message: 'm2' },
{ date: '2019-12-02', message: 'm3' },
{ date: '2019-12-03', message: 'm4' },
{ date: '2019-12-03', message: 'm5' },
{ date: '2019-12-03', message: 'm6' }
]
// このように日付でグルーピングしたい
[
{ date: '2019-12-01', messages: ['m1', 'm2'] },
{ date: '2019-12-02', messages: ['m3'] },
{ date: '2019-12-03', messages: ['m4', 'm5', 'm6'] }
]
// reduce
const messageGroup = messages.reduce((arr, current) => {
const foundRow = arr.find(v => v.date === current.date)
if (foundRow) {
foundRow.messages.push(current.message)
} else {
arr.push({ date: current.date, messages: [current.message] })
}
return arr
}, [])
意地でも一回の代入で仕上げたい人向けです。
forEachで書くと以下のようになります。
reduceの仕様を覚えていない人も少なくないかもしれませんが、さきほどとやっていることは同じです。
// forEach
const messageGroup = []
messages.forEach(current => {
const foundRow = arr.find(v => v.date === current.date)
if (foundRow) {
foundRow.messages.push(current.message)
} else {
arr.push({ date: current.date, messages: [current.message] })
}
})
おわり
おわりです!