javascriptを書いていて、requireの書き方で、ちょっとハマったのでメモ。
次のような要件を実装していくとします。
子要素を生成することができるclass Person{}をつくる。
要素を生成する機能は、class外に別途factory()メソッドを用意する。
Personにはchild()関数があり、factory()を実行して、子要素を作れる。
この仕組みを1ファイルですべて記述すると成功するのだけど、
ファイルを分割して、双方の関数をお互いがrequireしあうようにすると、
書き方によっては、そんな関数無いですよ!?と怒られます。
TypeError: factory is not a function
では。そのコードを紹介します。
続きを読む »
一つ前のポスト で、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で処理が複雑になれば、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);
その他の例についても、先のページがやっぱりわかりやすいので、一読をオススメ。
https://noah.plus/blog/007/
実例はこちら
実際、この操作は、カスタムフックでページネーションを作っていて、この症状が発生したのだけど、
別エントリにしまとめてみたので、よかったら合わせてどうぞ。
カスタムフックの設計指針についてページネーションで考える
自分は、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の事は触れられています。ただ、詳細にまでは触れられていないですね。
表題のとおり。Interventionで日本語などのマルチバイト画像文字を表示しようとする際に、日本語だと、エラーが出る場合がある。その解決方法。
続きを読む »
Dockerの基本的なコマンドのまとめです
続きを読む »
Dockerを使っているといつのまにかディスク容量をくっているので、たまには整理しようと、「$ docker images 」を実行し、これまでに作成したDockerイメージの一覧を見て確認している。その際に表示される、<none>のdockerイメージは削除してよいのか?noneイメージってなんなのか?という話。
Dockerfileを作成時に、「$ docker build -t <イメージ名> . 」と、何回もbuild繰り返しやり直していると次のように、<none>のdockerイメージがそのビルド回数分作成されてしまう。
続きを読む »
この記事はソフトウェアエンジニア向けにローカルでDockerで開発環境を作る際の、基礎知識のうち自分用にまとめたものです。日常的にdockerを使うようになったものの、何回もDockerの記事をみては忘れて、見てはわすれてを繰り返すので。
本番環境などでの利用、クラウドでの利用などについては本記事で触れません。
ネットの記事や、オライリー本やいくつかのDocker関連書籍をみましたけど、ダントツでわかりやすかった入門者向けのおすすめの書籍を紹介しておきます。どの書籍でも、セキュリティやk8sなどまで触れられてますね。
Docker Engine/ Docker Machineってなに
Dockerを使うということは、Docker Engineを使うということ。
コンテナから、ホストOSのカーネルを使うことができるというDocker Engineのおかげ。注意としては、コンテナからホストOSのカーネルを使うということなので、コンテナに展開されるOSとホストのOSとは、本来同じといけない。(Docker上で、Linuxのコンテナを動かしたい場合は、ホストマシンはそのLinuxのOSである必要がある。)
特定のLinuxのOSの機能を使おうとしても、Windowsや、MacなどOSが異なれば動作しない。逆も然り、ホストマシンのOSがLinuxベースの場合、WindowsのDockerイメージを動かすことはできない。
MacなどでDockerを使う場合は、VirtualBoxなどでまずLinuxの仮想環境を用意し、そのOSに沿ったDockerイメージ/コンテナを展開といけないという話であるけども、わざわざVirtualBoxなどで仮想環境を作るのが面倒。そこで、Docker for windows、Docker for mac などのインストーラーが提供されている。
これらインストールすると、Dockerを起動するための様々なツールがインストールされる。その中に含まれる、Docker Machineによって、ユーザは仮想環境について意識せずとも、自動的にLinuxの仮想環境を構築され、ホストOSがMacであれWindowsであれ、LinuxベースのDockerコンテナを立ち上げることができるようになる。
ホストOSがMacでWindowsのコンテナを作成することはできない
ホストOSがWindowsでMacのコンテナを作成することはできない。
ホストOSがLinuxで、WindowsやMacのコンテナを作成することはできない。
Dockerfileとその記述について
Dockerはコンテナを作成するために、その雛形となるDockerImageを用意する必要がある。
Dockerfileは、そのDockerイメージを作成するための設定をまとめたファイル。
ファイル名は「Dockerfile」とするのが慣習的であるが(DockerImageを作成する際デフォルトで読み込んでくれるファイル名が「Dockerfile」)、設定ファイル名を指定することも可能。
実際の運用上では、開発環境と本番環境とをわけるように「Dockerfile.development」「Dockerfile.production」のように、環境に応じていDockerfileが複数用意されるケースもみられる。
なお、Dockerfileは1行1レイヤーとなりDockerイメージに積み重なっていくため、行数は可能な限り少なく抑えるほうが良い。
以下に、Dockerfileの命令文をいくつか乗せる。
FROM (<イメージ>:<タグ(バージョンなど)>)
ベースとなるDockerイメージを読み込む。
DockerHubで公開されているものを使うことがほとんど。
Google検索で、構築したい環境のDockerfileについて検索すると大抵すぐみつかる。
今回、例としてGo言語の開発環境を作りたかったとして、
GolangのDockerHubを見てみる。
Simple Tagsという所がタグなのであるが、非常に多く、どのバージョンを利用すべきか迷う。
そのページの下部の方に、説明が書かれているが、次のように性質が異なる。
golang:1.10
標準全部入り。
golang:1.10-alpine
最小限の構成のようなもの。
debianベース
golang:1.10-buster
最新のdevianパッケージの構成らしい。
さまざまなDockerfileなどをみていくと、その他に、`Buster、Stretch、Jessie`なども見かけるあるが、Debianのバージョンのコードネームのようだ。
https://wiki.debian.org/DebianReleases
自分は、go言語環境をつくっていて、Dockerfileで「go get」したかったので、gitが必要があったのだけど、容量を少なくおさえたいからといって、alpineのディストリビューションを利用すると、gitコマンドが使えない。。。
alpineを使い、gitコマンドを使いたければ、gitをapt-getでインストールしないといけない。
ベースイメージ
gitについて
イメージサイズ
golang:1.10
git使える
約1GB
golang:1.10-alpine
git使えない
約350MB
golang:1.10-alpine
git追加インストール
約700MB
自分は少しでも容量を小さくしたいので、alpine+gitインストールして使っている。
それ以外のイメージとしては、「FROM scratch」 とすると、Dockerを利用するにあたり、最も最小の構成から、環境を構築することも可能。イメージサイズは極小である。
WORKDIR
コンテナ上でのワーキングディレクトリ。たとえば、goの場合は「WORKDIR /go/src/demo」 apache2の場合は「WORKDIR /var/www/htdocs 」とするもよし、
COPY <ホストディレクトリ> <コンテナディレクトリ>
ホストマシン上の特定のディレクトリを、Dockerコンテナ内の特定のディレクトリにコピーする。「COPY . . 」とすると、現状のディレクトリ内を、上記で指定したWORKDIRにコピーする。
COPYににたADDというコマンドの場合は、コピーしたファイルのなかに.tarが含まれる場合はtarボールが解答される。
EXPOSE
ポートフォワーディングの設定 EXPOSE 8080 などとする。
CMD
コンテナを立ち上げた際に、デフォルトで実行するコマンド。CMD [“./sample_app”] とするとWORKDIR内に配置されているsample_appを実行する。
デフォルトに実行するコマンドなので、コンテナ起動時にコマンドを実行すると、コンテナ起動時のコマンドが優先されるので注意。
RUN
コンテナで実行したいコマンドをRUNの後に記述する。「RUN cd ../」など
上述のalpineディストリビューションの場合に、gitをインストールしてgo getをする例は次の通り。
RUN apk add --no-cache \
alpine-sdk \
git \
&& go get github.com/xxxx/xxxxx
その他
その他にも「ENTRYPOINT」や「ENV」などがあるが、今回は除外。
まとめ
以上がDockerfileについてのメモ。今後Dockerについて触れていきたい内容として、コマンドの最低限のまとめ、docker-composeマルチステージなどで、Dockerイメージの容量を圧縮する方法や、それに関しては後日記述してみたいと思う。
参考リンク
イベントって何かと便利だから、共通処理をイベント使って書きたくなります。
リダイレクト処理もそうしたい。って思ったのがきっかけ。
で、いろいろリスナ書いてみるも、できなかったので、解決策を書き残しておきます。
Event不可。MiddlewareかControllerに。
Eventはバックグラウンドタスクとしてコールされるので、リダイレクト処理描いても無駄だよって。
参考:firing a redirect from an event
だから、ログイン後に特殊なリダイレクトのロジックを挟みたいときは、
コントローラのログイン処理の後でリダイレクトのロジックを挟むか、
ミドルウェアにリダイレクトのロジックを記述
のいずれかにしましょう。
イベントリスト
イベントのリストはこちらにまとまってる。
概ね、次のようなところにあるよう。
Auth
Cache
Consoleコマンド
Databaseのトランザクション
Log
Mail
Notification
Queue
Redis
リファレンス本にもイベントのこと書いてたような気がするので、後でチェックする。
一つのクラスに対して、何かしらの機能を追加したい場合、継承する方法を単純に思いつきますが、
SwiftにProtocolとDelegateという考え方がありましたので、PHPに置き換えてみたいと思います。
Protocolはhttpとかhttpsとかそういった話ではなく、単に「ルール」といった意味あいのもので、
PHPのinterface、LaravelなどのframeworkではContractと言われてているやつです。
Delegateは、直訳だと委譲とか。そういうやつ。
Delegateという単語を見ると身構えてしまいますが、
Interfaceを意識したプログラミングしてると、知らずのうちに似たような事をしているというケースは結構ありそうです。
続きを読む »