未分類

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などを新たに用意して、キーワードを特定のキーで絞り込んだ結果をかえす処理をつくるのが汎用的に使いまわせそうに思いますね。

いろいろ試行錯誤して、自分なりのフックを作ってみてください。

Laradockでホストマシンの変更が反映されない場合の対策

by codechord. 0 Comments

自分は、MacOSで、Laradockを使って開発環境を構築している。
その際に、ホストマシンの更新内容がコンテナ内に反映されず、悩まされた。

自分のようにハマったような例もあまりみかけず。
一応動作することが確認したので、その経緯・設定を書き残しておきます。
(詳しくは把握できていないのでご了承)

laradock/.envの次の行が原因とおもわれるのだが、

laradock/.env

# You may add flags to the path `:cached`, `:delegated`. When using Docker Sync add `:nocopy`
APP_CODE_CONTAINER_FLAG=:cached

次のようにいずれの設定に切り替えたとしても、状況は改善されなかった。

APP_CODE_CONTAINER_FLAG=:delegated
APP_CODE_CONTAINER_FLAG=:nocopy

最終的には、設定を解除すると正常に動作するようになった。

APP_CODE_CONTAINER_FLAG=

なお、ぐぐってみると、同期が遅い。というケースはあるようで、その場合はdocker-syncを使いrsyncの仕組みで同期すると早いとあります。
また、公式ドキュメントによると、docker-composeコマンドを使うのではなく、「laradock/sync.sh」コマンドにて起動するとdocker-syncが起動すると書かれています。
sync.shも、難しいことがかかれているわけではないので、見てみるとよいですが「laradock/sync.sh」、次のような処理になっています。

$ ./sync.sh up ~~~
▼実際の処理
$ docker-sync start
$ docker-compose up -d ~~~~~
$ ./sync.sh down
▼実際の処理
$ docker-compose stop
$ docker-sync stop

起動前にはdocker-syncを開始して、終了時にはdocker-syncを止めると、それだけですね。
ただ、ちょっと気になっているのは、終了時、「docker-compose down」しているのではなく、「docker-compose stop」している点。downだと、コンテナまで削除してくれるけど、stopだとサービスを停止するだけで、コンテナはのこったままになる。
コミットログを辿ってみると、意図的に、docker-compose downではなく、docker-compose stopしているよう。

Don't use docker-compose down to stop containers

This delete containers and volume too. 
Use docker-compose stop who only stop containers

うーーん、意図がわからない。

リファレンス本にもLaradockの事は触れられています。ただ、詳細にまでは触れられていないですね。

SwiftのDelegateをPHPで使って見る

by codechord. 0 Comments

geralt / Pixabay

一つのクラスに対して、何かしらの機能を追加したい場合、継承する方法を単純に思いつきますが、
SwiftにProtocolとDelegateという考え方がありましたので、PHPに置き換えてみたいと思います。

Protocolはhttpとかhttpsとかそういった話ではなく、単に「ルール」といった意味あいのもので、
PHPのinterface、LaravelなどのframeworkではContractと言われてているやつです。

Delegateは、直訳だと委譲とか。そういうやつ。
Delegateという単語を見ると身構えてしまいますが、
Interfaceを意識したプログラミングしてると、知らずのうちに似たような事をしているというケースは結構ありそうです。

続きを読む »