ページング用データ加工とは何か
ページングは「長い配列をページ単位に切り分けて、必要な部分だけ表示・送信する」処理です。ここが重要です:順序(ソート)→フィルタ→ページ分割の“処理順”を守り、メタ情報(総件数、総ページ、現在ページの開始・終了インデックス)を一緒に返すと、UI・APIの両方で扱いやすくなります。
基本のページング(ページ番号+件数)
スライスで“取り出す範囲”を決める
function paginate(list, page = 1, perPage = 20) {
const total = list.length;
const totalPages = Math.max(1, Math.ceil(total / perPage));
const current = Math.min(Math.max(1, page), totalPages); // 範囲に収める
const start = (current - 1) * perPage;
const end = start + perPage; // slice は end 未満
const rows = list.slice(start, end);
return { rows, page: current, perPage, total, totalPages, start, end: Math.min(end, total) };
}
// 使い方
const data = Array.from({ length: 53 }, (_, i) => i + 1);
paginate(data, 3, 10); // 21..30 が rows
JavaScriptここが重要です:page の境界を必ず正規化(1〜totalPages)し、slice の範囲を一貫して計算します。end は“未満”であることに注意。
フィルタ・並び替えとページングの順序
まず整形(フィルタ・ソート)してからページング
function applyFiltersAndPaging(list, { q, sort, page, perPage }) {
// フィルタ(例:名前に q が含まれる)
const normalize = s => s.trim().toLowerCase();
const filtered = q
? list.filter(x => normalize(x.name).includes(normalize(q)))
: list;
// 並び替え(例:価格昇順/降順)
const sorted = sort === "priceDesc"
? filtered.toSorted((a, b) => b.price - a.price)
: filtered.toSorted((a, b) => a.price - b.price);
// ページング
return paginate(sorted, page, perPage);
}
JavaScriptここが重要です:フィルタやソートの後にページングしないと、ページ境界がズレて“見えるはずのデータが消える”問題が起こります。常に「整形→ページング」の順。
ページングメタの設計(UIが使いやすい補助情報)
次・前ページの可否、ページボタンなど
function withNav(meta) {
const { page, totalPages } = meta;
return {
...meta,
hasPrev: page > 1,
hasNext: page < totalPages,
prevPage: page > 1 ? page - 1 : null,
nextPage: page < totalPages ? page + 1 : null
};
}
// 例
const result = withNav(paginate(data, 1, 10));
JavaScriptここが重要です:UIが“条件分岐なし”でボタン制御できるよう、hasPrev/hasNext などのフラグを返すと使い勝手が上がります。
サーバー/クライアントでの違い(offset と cursor)
オフセット型(page/perPage)—シンプルで理解しやすい
// クエリ例:?page=3&perPage=20
const start = (page - 1) * perPage;
const rows = list.slice(start, start + perPage);
JavaScriptここが重要です:並びが安定している(ソート固定)の前提で有効。大量データでは“後ろのページほど重い”課題があり、サーバー側ではインデックス最適化が必要です。
カーソル型(cursor/limit)—大量・変更に強い
// 疑似例(client側):前回の最後のIDをカーソルに
function pageByCursor(list, cursorId, limit = 20) {
const startIndex = cursorId ? list.findIndex(x => x.id === cursorId) + 1 : 0;
const rows = list.slice(startIndex, startIndex + limit);
const nextCursor = rows.at(-1)?.id ?? null;
return { rows, nextCursor, limit };
}
JavaScriptここが重要です:データの追加・削除があっても重複・取りこぼしが起きにくいのが利点。API実務ではカーソル+安定ソート(IDや作成日時)をセットにします。
エッジケースと安全策(重要ポイントの深掘り)
空・最終ページ・perPage 変更の取り扱い
- 空配列なら totalPages=1 として page=1 を返すとUIが扱いやすい。
- perPage を動的に変えたら、page を再正規化(大きすぎるページは最終ページへ)する。
function safePaginate(list, page, perPage) {
const per = Math.max(1, perPage | 0);
const meta = paginate(list, page, per);
return withNav(meta);
}
JavaScript欠損や型揺れの防御
ページ/件数は整数へ正規化(| 0 や parseInt)、負数や0は最小値へ丸める。文字列のソートは localeCompare、数値は a - b に統一。
フィルタ・ソートの重さを軽減(前計算)
頻繁にページングするなら、比較に使うキー(normalize済み文字列、new Date(...).getTime()など)を前計算して持たせ、スライス前に高速に判定できるようにする。
const prepared = list.map(x => ({ x, key: x.name.toLowerCase() }));
const filtered = prepared.filter(p => p.key.includes("book")).map(p => p.x);
JavaScript実務で便利な拡張(ヘッダ・フッタ・合計・ページ行番号)
ページ内の“連番”や“行番号”を付ける
function paginateWithIndex(list, page, perPage) {
const meta = paginate(list, page, perPage);
const rows = meta.rows.map((x, i) => ({ ...x, rowNo: meta.start + i + 1 }));
return { ...meta, rows };
}
JavaScriptここが重要です:ページ内に通し番号があると、UIで参照が楽になり、CSV出力などにも向きます。
ヘッダ・フッタ用の要約(合計など)
function summarizePage({ rows }) {
const sum = rows.reduce((a, x) => a + (x.price ?? 0), 0);
return { sum, count: rows.length };
}
JavaScriptここが重要です:ページ内集計は“切り出して”返すと、UIが直で使えます。総合計は全体から別途計算します。
すぐ使えるレシピ(定番ユーティリティ)
ページング(メタ付き・安全版)
const paginate = (list, page = 1, perPage = 20) => {
const per = Math.max(1, perPage | 0);
const total = list.length;
const totalPages = Math.max(1, Math.ceil(total / per));
const current = Math.min(Math.max(1, page | 0), totalPages);
const start = (current - 1) * per;
const end = Math.min(start + per, total);
return {
rows: list.slice(start, end),
page: current,
perPage: per,
total,
totalPages,
start,
end,
hasPrev: current > 1,
hasNext: current < totalPages,
prevPage: current > 1 ? current - 1 : null,
nextPage: current < totalPages ? current + 1 : null
};
};
JavaScript整形→ページング(フィルタ+ソート+ページ)
const normalize = s => s.trim().toLowerCase();
function querySortPage(list, { q, sort = "nameAsc", page = 1, perPage = 20 }) {
const filtered = q ? list.filter(x => normalize(x.name).includes(normalize(q))) : list;
const cmp =
sort === "priceAsc" ? ((a, b) => a.price - b.price) :
sort === "priceDesc" ? ((a, b) => b.price - a.price) :
((a, b) => a.name.localeCompare(b.name, "ja", { numeric: true }));
const sorted = filtered.toSorted(cmp);
return paginate(sorted, page, perPage);
}
JavaScriptカーソル型(次ページ用の印を返す)
function cursorPage(list, cursorId = null, limit = 20) {
const i = cursorId == null ? -1 : list.findIndex(x => x.id === cursorId);
const start = Math.max(0, i + 1);
const rows = list.slice(start, start + limit);
return { rows, nextCursor: rows.at(-1)?.id ?? null, limit };
}
JavaScriptまとめ
ページング加工の核心は「整形(フィルタ・ソート)→ページ分割→メタを返す」という流れを徹底することです。page/perPage を正規化し、slice の範囲を一貫して計算、UI向けに hasPrev/hasNext や行番号などの補助情報を付ける。大量・変動データにはカーソル型が有効で、重い処理は前計算で軽くする。これを押さえれば、初心者でも読みやすく、拡張しやすい、実務に耐えるページング処理が書けます。
