React
Reactカスタムフックの設計指針についてページネーションで考える
by codechord.0 Comments

Pexels / Pixabay
一つ前のポストで、useMemoがうまく動作しないっていう症状について書きましたが、
複雑な要件ではなく、コンポーネントの値をsort、sliceし描画するだけであれば、特に問題はおこらないです。
正確には、問題は問題だけど一応動作する。というのが正式な回答かな。
結局使いまわししたいということに尽きるので、特定の責務に特化したカスタムフックを作り、値を受け渡すことで、さまざまな場面で使い回せるようにするのが良いと思っています。
さて、今回はカスタムフックの組み立て方について思うことを書き残します。
責務を考える
基本は、OOPの特定の責務のクラスを連携してモジュールを作っていく感じですけど、ファサードに当たる部分だったりを、カスタムフックに担わせて、必要最低限のインターフェースをコンポーネント側に提供するっていう感覚を自分は持っています。
コンポーネント間でロジックも分離されることになるので、カスタムフック自体が複雑な大きなボリュームになるとは想定しにくいですし、そういう複雑な処理は別途ライブラリを作るなり読み込んで使うなりするとおもうので、カスタムフックの範疇からはそれてくるのかな?という印象。
Real world
ページネーションの実装を元に考えてみたいと思います。
先のポストで、immutableな関数を通した値の加工に触れましたが、
sortやslice処理後のオブジェクトの受け渡しもここで、発生するので、あわせてみてください。
さて、ページネーションを実装していくにあたり、様々なケースで使い回せるような設計にするためには、リストアイテムを引数に渡すことで、うまく加工されるという2つのカスタムhookを作ることが考えられます。
- const {sortedItems} = useSort(items):並び替えするカスタムフック
- const {filteredItems} = useFilter(items):件数を絞り込むカスタムフック
もう少し考えると、Filterする際に、何ページ目なのか、合計何ページあるのかといったインターフェースも提供したいので、useFilterではなく、usePaginationとするほうがベターのようにも思います。
- const {filteredItems, paginationElement} = usePagination(items):件数を絞り込むみ、ページネーションを生成するカスタムフック
Sample
では、以上を元に、コードにおとすだけですね。
codesandboxに、App.jsの1枚にまとめましたのでみてください。
ただ、このままではページネーションとしては動きませんので、あくまでも参考と慣れば幸いです。
そっくりそのままおいておきます。
今日はこの辺で。
import React from "react";import { useCallback, useState, useMemo } from "react";export default function App() { const [items, setItems] = useState([1, 2, 3, 4, 5, 6]); const { sortedItems, toggleSort } = useSort(items); const { filteredItems } = usePagination(sortedItems); return ( <div> This is sample <button onClick={() => { toggleSort(); }} > トグル </button> <ul> {filteredItems.map((val, index) => { return <li key={index}>{val}</li>; })} </ul> </div> );}//並び替えするカスタムフックconst useSort = (items) => { const [isDesc, setIsDesc] = useState(false); const sortedItems = useMemo(() => { //悪い例 const badExampleItems = isDesc ? items.sort(_sortDesc) : items.sort(_sortAsc); //良い例 const goodExampleItems = isDesc ? [...items].sort(_sortDesc) : [...items].sort(_sortAsc); return goodExampleItems; }, [items, isDesc]); // DESC/ASCの切り替え const toggleSort = useCallback(() => { setIsDesc((prevState) => { return !prevState; }); }, [setIsDesc]); return { sortedItems, toggleSort };};function _sortAsc(a, b) { if (a < b) { return -1; } if (a > b) { return 1; } return 0;}function _sortDesc(a, b) { if (a < b) { return 1; } if (a > b) { return -1; } return 0;}//フィルタするカスタムフックconst usePagination = (items) => { const [limit, setLimit] = useState(3); const [currentPage, setCurrentPage] = useState(1); const filteredItems = useMemo(() => { //悪い例 const badFilteredItems = items.slice(0, limit); //良い例 const goodFilteredItems = [...items].slice(0, limit); return goodFilteredItems; }, [items, limit]); const paginationElement = useMemo(() => { // console.log(items, currentPage, limit); let li_list; // ページネーションのLiタグリストを生成する処理 // ... return (<ul>{li_list}</ul>); }, [items, currentPage, limit]); return { filteredItems, paginationElement, setCurrentPage, setLimit };};汎用性を考える
責務を考えるにつながる話だけど、フック作るならば、極力汎用的なほうがよい。
たとえば、先の例でいうと、useSortというフックは、ページネーションの要素に関わらず、ありとあらゆるリストの並び替えに利用できるほうがよいし、useFilterも、ページネーション関わらず何らかの絞り込みができるとよい。
なおかつ、その結果をusePaginationと互換性がある形にしておけば、ページネーションにもスムーズに適応できるようになる。
こういう設計を考えた際には、カスタムフック内に、useContextなどグローバルで共有されるような仕組みを使って更新するという処理は向かず、フックの外からStateを更新するような処理を注入するしてあげるほうが、汎用性は上がりますね。
汎用性を必要としない要件(たとえば認証など)であれば、hook内で認証状態をcontextなどを使ってグローバル管理ちゃって良いと思います。
拡張方法を考える
完成形は用意していませんが、完成形としては、
- ページの切り替え
- 表示件数の変更
などが実装できて完成だと思いますが、ページネーションの責務だと思いますので、usePaginationにそのインターフェースなども実装するのが良いと思います。
- キーワードでの絞り込み
の要件を考えるとしたら、usePaginationの中に内包するよりかは、useSearchFilterなどを新たに用意して、キーワードを特定のキーで絞り込んだ結果をかえす処理をつくるのが汎用的に使いまわせそうに思いますね。
いろいろ試行錯誤して、自分なりのフックを作ってみてください。
Reactで、Array.sortしたデータが、useMemoやuseCallbackに反映されない
by codechord.0 Comments

Monsterkoi / Pixabay
Reactで処理が複雑になれば、useMemoやuseCallbackなどを使い、描画コストがかからないよう効率化していくわけでが、
ページネーションを作っていて、その際、useMemoの値が更新されない症状が発生したのでそのメモ。
Immutableであれ
結構、Reactなどで重要な、immutabuleであることがポイントで、破壊的な関数をつかってたのが原因。Immutableじゃないぞ!っていうエラーが出るわけもなく、ちょっとハマった。
immutableをわかったつもりでわかっていなかったんだと思う。
immutabuleについては、こちらの記事が、完結でわかりやすい。
https://noah.plus/blog/007/
配列/オブジェクトの正しい加工
具体的には、並び替えをする処理に、Array.sortやArray.sliceを、そのまま使って加工していた。
だめなコード例は、次のような感じ
items.sort(並び替えの関数);items.slice(start,end);
次のように、加工前に、あらたなオブジェクト(配列)を作ってしまうことで、元のオブジェクトを壊さないで済む。
[...items].sort(並び替えの関数);[...items].slice(start,end);
その他の例についても、先のページがやっぱりわかりやすいので、一読をオススメ。
実例はこちら
実際、この操作は、カスタムフックでページネーションを作っていて、この症状が発生したのだけど、
別エントリにしまとめてみたので、よかったら合わせてどうぞ。
POPULAR ENTRY
- README.mdファイル。マークダウン記法まとめ
- 一石三鳥!?SublimeText2とsass/SCSS(compass)+SFTP(FTP)で作業効率化する方法。
- 3つの簡単便利Javascriptテンプレートエンジン。Mustache.js, Handlebars.js, Microtemplating
- Sublime Text 2とCtagsの組み合わせがすごい。開発スピードUP[Mac版]
- MacのApp Storeに接続出来ない場合の対処法
- [CMS]多言語化、翻訳ファイル.po、.mo。
- WordCamp vancouver 2012へ行ってきた、まとめ
- パララックスでのイベントの考察。scroll / throttle / debounce / touchmove / mousewheel
- docker imagesに表示される<none>を消す。dangling
- CodaとZen-Codingのショートカットまとめ。極めると早い。
RECENT ENTRY
PROJECT
CATEGORY
ARCHIVE
- 2020年10月 (3)
- 2019年9月 (3)
- 2019年8月 (2)
- 2019年5月 (1)
- 2019年3月 (1)
- 2018年12月 (1)
- 2018年9月 (1)
- 2018年7月 (1)
- 2018年4月 (1)
- 2016年12月 (2)
- 2016年2月 (1)
- 2016年1月 (1)
- 2015年12月 (1)
- 2015年8月 (1)
- 2015年6月 (1)
- 2015年5月 (2)
- 2014年9月 (1)
- 2014年6月 (1)
- 2014年1月 (1)
- 2013年7月 (1)
- 2013年3月 (2)
- 2012年12月 (2)
- 2012年10月 (1)
- 2012年1月 (3)
- 2011年7月 (1)
- 2011年6月 (2)
- 2011年2月 (1)
- 2010年6月 (4)