この記事は NTTコムウェア Advent Calender 2024 20日目の記事です。
はじめに
NTTコムウェアの中原 です。
最近は SmartCloud® オーケストレータ の開発チームで React の MUI (Material-UI) を触っております。
表にデータを出力したいとき、とりあえず Data Grid を使うことが多いのですが、ソート機能のカスタマイズが少し難しかったので備忘録として整理してみます。
本記事は 公式ドキュメント と重複する部分もありますので、そちらもご覧ください。
ソートの基本
配列におけるソートの基本的な仕組みは、要素を2つ取り出して値の比較ですね。
JavaScript の sort()
を用いた簡単な例を示します。
返り値がゼロ、正、負のいずれであるかによって処理が変化します。
昇順
const numbers = [5, 2, 9, 1, 5, 6]
numbers.sort((v1, v2) => v1 - v2) // ここの返り値が重要
/*
2 と 5 を比較: 2 - 5 = -3 (2を5の左へ)
9 と 6 を比較: 9 - 6 = 3 (6を9の左へ)
5 と 5 を比較: 5 - 5 = 0 (変更なし)
...
*/
console.log(numbers) // [1, 2, 5, 5, 6, 9]
降順
const numbers = [5, 2, 9, 1, 5, 6]
numbers.sort((v1, v2) => v2 - v1) // ここの返り値が重要
console.log(numbers) // [9, 6, 5, 5, 2, 1]
サンプルの準備
適当なオブジェクト配列 rows
と columns
を用意し、Data Grid に出力できたら準備完了です。
x-data-grid
のバージョンは 7.6.1
です。
import React from 'react'
import Box from '@mui/material/Box'
import { DataGrid } from '@mui/x-data-grid'
const rows = [
{ id: 1, name: 'Apple', price: 1000, stock: 20 },
{ id: 2, name: 'Orange', price: 2000, stock: 15 },
{ id: 3, name: 'Peach', price: 1500, stock: 10 },
{ id: 4, name: 'Banana', price: 500, stock: 30 },
{ id: 5, name: 'Grape', price: 3000, stock: 5 },
{ id: 6, name: 'Lemon', price: 2500, stock: 8 }
]
export default function App() {
const columns = [
{ field: 'id', headerName: 'ID' },
{ field: 'name', headerName: 'Name'},
{ field: 'price', headerName: 'Price'},
{ field: 'stock', headerName: 'Stock'}
]
return (
<Box>
<DataGrid
rows={rows}
columns={columns}
/>
</Box>
)
}
sortComparator
の基本
sort()
と同じ要領で、昇順・降順のソートを実装します。
columns
の要素に sortComparator
プロパティを追加し、引数 (v1,v2)
と返り値 v1 - v2
を書きます。
これだけで昇順・降順の両方をカバーしています。
返り値 v1 - v2
より、昇順にしかならないようにも見えますが、降順の場合は返り値の符号が反転するよう Data Grid が処理します。
(人によっては余計なお世話かも?)
const columns = [
{ field: 'id', headerName: 'ID' },
{ field: 'name', headerName: 'Name'},
{
field: 'price',
headerName: 'Price',
sortComparator: (v1, v2) => {
return v1 - v2
}
},
{ field: 'stock', headerName: 'Stock'}
}
]
昇順・降順を明示すると次のようになります。
sortModel
プロパティを用いてソート状態を管理し、昇順・降順の条件分岐ができます。
customSortComparator
の返り値を色々いじってみてソートを試してみるのも良いと思います。
const customSortComparator = (sortModel) => (v1, v2) => {
if (sortModel.length === 0) {
return 0 // ソートしない
} else {
if (sortModel[0].sort === 'asc') {
return v1 - v2 // 昇順
} else if (sortModel[0].sort === 'desc') {
return v1 - v2 // 降順(v2 - v1 ではないことに注意)
}
}
}
export default function App() {
const [sortModel, setSortModel] = React.useState([])
const handleSortModel = (newSortModel) => {
setSortModel(newSortModel)
}
const columns = [
{ field: 'id', headerName: 'ID' },
{ field: 'name', headerName: 'Name'},
{
field: 'price',
headerName: 'Price',
sortComparator: customSortComparator(sortModel)
},
{ field: 'stock', headerName: 'Stock'}
]
return (
<Box>
<DataGrid
rows={rows}
columns={columns}
sortModel={sortModel} // sortModelプロパティを追加
onSortModelChange={handleSortModel} // ハンドラを追加
/>
</Box>
)
}
sortComparator
の応用
ここまでで話が終わってしまうと、わざわざ sortComparator
を使う意味がありません。
昇順・降順のソートは Data Grid に標準搭載されているからです。
ようやくスタートラインに立ちました!ということで、ここからはカスタマイズの小技を紹介します。
特定の行を固定する
例えば、最上段の行、すなわち id === 1
の行を固定してみます。
id === 1
の行が来たときに、返り値に 0
を与えるよう条件分岐します。
id
を用いるため、引数に row1, row2
を追加します。
const customSortComparator = (sortModel) => (v1, v2, row1, row2) => {
if (sortModel.length === 0) {
return 0 // ソートしない
} else {
if (sortModel[0].sort === 'asc') {
if (row1.id === 1 || row2.id === 1) {
return 0 // 先頭行を固定
} else {
return v1 - v2 // 昇順
}
} else if (sortModel[0].sort === 'desc') {
if (row1.id === 1 || row2.id === 1) {
return 0 // 先頭行を固定
} else {
return v1 - v2 // 降順
}
}
}
}
特定の行を下方に追いやる
例えば、データが歯抜けになっていて undefined
で与えられる場合、式 v1-v2
を評価できません。
{ id: 3, name: 'Peach', stock: 10 } // price が定義されていない場合
ソートしたときに邪魔なので、 undefined
の行を強制的に下方に送るようにします。
const customSortComparator = (sortModel) => (v1, v2) => {
if (sortModel.length === 0) {
return 0 // ソートしない
} else {
if (sortModel[0].sort === 'asc') {
if (v1 === undefined) {
return 1 // v1 が未定義のとき、v1 を下方へ
} else if (v2 === undefined) {
return -1 // v2 が未定義のとき、v2 を下方へ
} else {
return v1 - v2 // 昇順
}
} else if (sortModel[0].sort === 'desc') {
if (v1 === undefined) {
return -1 // 1 ではないことに注意
} else if (v2 === undefined) {
return 1 // -1 ではないことに注意
} else {
return v1 - v2 // 降順
}
}
}
}
おわりに
Data Grid は MUI の中でも主力のコンポーネントなので、機能がかなり充実しています。ただし、痒いところに手が届くような機能は Pro や Premium といった有料プラン限定です。
紹介しておいてこう言うのも憚られますが、例えば Row pinning があればソートのカスタマイズは必要ないかもしれません。
「SmartCloud(スマートクラウド)」は、NTTコムウェア株式会社の登録商標です。
その他、記載されている会社名、製品名、サービス名は、各社の商標または登録商標です。