やりたいこと
Hooks がリリースされてから、コンポーネントのシンプルな状態管理に関してはuseState
などで済ませられるようになったが、やや複雑なコンポーネントの状態管理+アルファを Hooks でやってみたかった。
つくったもの
今回はuseReducer
を使って市町村名をリストで表示するページネーションを実装してみた。
サンプルコード:https://codesandbox.io/s/paginator-demo-mnxvc
UIフレームワークは Material UI でサクッと用意。
useReducer を使った例としては公式のリファレンスでもカウンターなどがありますが、もう少し実用的で、複雑な状態管理と副作用をうまく利用することが要求されるコンポーネントを作って試してみたいと思っていたので今回それをやりました。
ページネーションを題材とした理由は、主に
- コンポーネントの操作で非同期にデータをGETする必要性がある
- ページの始点・終点・中間点等でコンポーネント(「進む」「戻る」などのボタン、ページ位置の表示)の制御を切り替える必要がある
- 実際のWebアプリケーションでも使用される機会が多い
という点。その他に1ページあたりの表示件数とか、データの表示スタイルの切り替えといったアレンジも加えやすいので、思いつく限り今回は機能を盛り込んでみました。
※今回はサンプルコードを全て公開している都合上、公開APIを叩いて非同期でデータをGETするかわりに静的なデータ(市町村名とIDの一覧)を用意し擬似的に何件かずつarray.slice
で切り出して表示するという実装に変えてあります。
ソースコードとコンポーネントについて
詳しくは上記の CodeSandbox の中身をみていただくとして、今回はページネーションのコンポーネントとReducerの動きについて軽く説明します。
<Paginator />
コンポーネント
初期Stateは以下のように設定しています。
function App() {
return (
<div className="App">
<Container fixed>
<Paginator
sum={cities.length} //データの総件数
per={10} //1ページあたりの表示件数
initialData={cities.slice(0, 10)} //読み込み時に表示するデータ
component={ListComponent} //データを渡すPresentaionコンポーネント
/>
</Container>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<Button>
前へ・次へ
ページの操作を行います。
「前へ」ボタンがクリックされたら前n件のデータとともにviewPreview
という関数を、「次へ」ボタンがクリックされたらviewNext
という関数を発行します。
const reducer = (state, action) => {
switch (action.type) {
//[前へ]ボタンをクリック時に発行。
//ページ数のデクリメント
case "viewPreview":
return {
...state,
currentPage: state.currentPage - 1,
resourceData: action.data
};
//[次へ]ボタンをクリック時に発行。
//ページ数のデクリメント
case "viewNext":
return {
...state,
currentPage: state.currentPage + 1,
resourceData: action.data
};
.
.
.
}
};
reducer は発行される直前のstate
とdispatch()
の中身(≒変更するstate)をaction
として引数にとります。変更を加えないstateは...state
とスプレッド演算子を使って return しないと上手くコンポーネントが機能しないため忘れずに。
<Select>
1ページあたりの表示件数の切り替え
Selectボックスを変更した場合、setPerAmount
という関数を発行します。
//reducer
//表示件数の切り替え時に発行。
//現在のページを1ページ目にリセット。
case "setPerAmount":
return {
...state,
currentPage: 1,
per: action.per,
pageAmount: Math.ceil(state.sum / action.per),
resourceData: action.data
};
表示件数を切り替えた場合は一番先頭のデータからまた表示し直すことが良い?気がするのでaction
に入っているデータは先頭からn件の内容です。
<Switch>
表示形式(コンポーネントのスタイル)を切り替え
初期状態では市町村名とIDが2段組になったリスト形式で表示していますが、スイッチを切り替えることでシンプルな1行のコンポーネントで現在のページの内容を表示し直します。
スイッチの切り替え時にデータを1行ずつ表示するための子コンポーネントとswitchComponent
という関数を発行します。
//reducer
//表示形式の切り替え時に発行。
case "switchComponent":
return {
...state,
component: action.component
};
最後に
公式リファレンスを読むと使い方がなんとなくわかった気になりますが、実務ではカウンターやTodoリスト以上に複雑な機能を扱うことの方が多いためどういう場面で Hooks の力を最大限利用できるのかはまだ手探り中です。今回はuseContext
抜きで実装しましたが、扱うコンポーネントの数が増えてきたら避けては通れなくなるかも。