formikを使ってみる
前提
$ node --version v.9.11.1
React, ReduxでSPAを作っている。
背景
connectで渡されたpropsをComponentで加工してFormを作っている場合、Formを触って変更された値をStoreまで持っていっても、その値はあくまでもFormのみで有効な値であるから、Storeに反映させる変更は無いことに気付いた。
例えば、read・write・ execの3つの権限をチェックボックスで付けたり外したりできるFormを考えるとわかりやすい。connectでは3つの権限のうち、既に付いている権限だけが ["read", "exec"]
のように配列で渡されてくる。Componentではこれを {read: true, write: false, exec: true}
のような辞書に変換してチェックボックスを作る。ここからが重要で、チェックボックスをクリックすると onChange
に登録されたイベントハンドラには {read: false}
のような辞書が引数として渡されることになる。しかし、これをStoreまで引きずっていってもどうしようもないのである。Storeが欲しいものはread・write・execの全ての状態がどうなったかという、Formで最終的に生成された状態であって、「readがfalseになりました」というような断片的な情報には興味が無い。もちろん {read: false}
を受け取って ["read", exec"]
を ["exec"]
に変更することはできる。しかし、今回はたまたま簡単にハンドリングできるだけであって、より複雑なFormでは現実的でないだろう。
解消方法としては2つ思いついた
- StoreにFormごとのstateを持たせる
- reactのstateを使う
1は論外だろう。あり得ない。というわけで2番を採用する方向でReduxを利用しているときのReactのstateの利用有無について調べていたら、どうやらこれがReact界隈でいうところの Global State vs Local State という議論らしい。
ReduxにおけるGlobal stateとLocal stateの共存 - LIVESENSE ENGINEER BLOG
まあ現実的にはLocal Stateを解禁するしかないだろうと思ったが、一応社内のフロントエンジニアに聞きに行ったら今は「Ephemeral Stateという概念があるよ」と言ってFromikを教えてくれた。
GitHub - jaredpalmer/formik: Build forms in React, without the tears 😭
3行でどういうものか説明して、と頼んだらこういうものだと言う。
- formのような一時的なstate管理はreduxで管理しなくていいよ
- 代わりにpropsをformのstateにmappingしてくれる機能を用意したよ
- formを送信するタイミングでformのstateをreduxに流し込むよ
つまり、ComponentのstateでもなくStoreのstateでもない中間層を用意してそこにFormの中間状態を保持してsubmitのタイミングでStoreに渡してくれるようだ。
欲しかったものっぽいので試してみる。
BasicForm
公式のデモのBasicがシンプルなフォームを作っていてわかりやすかったので、適当に不要な所を省いて写経した。
formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub
使ってみた感じは彼が教えてくれた3行そのままだった。その他に気付いたことは、handleChange
やhandleReset
など良く使うハンドラは既に定義されていて一般的な振る舞いをやってくれる。また、特殊な動きがしたければ簡単に上書きも可能というもののようだった。中間状態はFormikの定義しているvalues
が担うようだ。
背景で説明したread・write・execのFormを実装するならば、wirhFormikの mapPropsToValues
で配列から辞書に変換して、handleSubmit
でvalues
から辞書を取り出して配列に戻してActionCreatorを呼び出すようになるかなと思った。
PermissionSelectorForm
背景に書いた、read・write・execのFormにFormikを適用してみたい。このFormをPermissionSelectorFormと呼称することにする。
Reactだけで実装
多分こんな感じになる。
formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub
Formikを適用する
多分こうなる。
formik-tutorial/index.js at master · roronya/formik-tutorial · GitHub
ハマりどころ
handleChangeかsetFieldValueか
最初はinputのonChange
にhandleChange
を渡していたが動かなかった。handleChange
は<input type=text>
のような逐次変わる値を追従させるときに使うようだ。<input type=checkbox>
みたいなやつはsetFieldValue
を使う。
また、setFieldValue
の第一引数は厄介だった。withFormic
のmapPropsToValues
で作っているprops
の構造を文字列で書く必要がある。props
の構造を複雑にしていると大変だ。
setFieldValueの使い方を良くよむこと。
GitHubのissueにcheckbox使う場合の例がcodesandboxで置いてくれているのが参考になった。 How to populate array with checkboxes? · Issue #360 · jaredpalmer/formik · GitHub
上書きできるのはhandleSubmitだけ?
↑でハマっていたときにhandleChange
をwithFormic
で上書きしてしまえばいいのでは?と思ってためしたができなかった。上書きできるのはhandleSubmit
だけらしい。
というわけでFormikでやりたいことができていい感じだった。おすすめ。
prettierがいい感じ
背景
WebStormのフォーマッターがいけてない。例えば
import React from 'react'
としたら
import React from 'react';
とセミコロン付けて欲しいが付けてくれない。 ほかにもダブルクオーテーションとクォーテーションを統一してくれない、とか、細かい不満がいっぱいあった。
Prettier
適当にググったらこれが出てきた。
https://qiita.com/furuk4wa/items/5f5a28e81a15653af0c8
名前だけ分かれば後は公式を見る。
install
$ yarn add prettier --dev --exact
https://prettier.io/docs/en/install.html
WebStormとの連携
公式に説明がある。
https://prettier.io/docs/en/webstorm.html
WebStormのバージョンが2018.1以上なら Alt-Shift-Cmd-P でフォーマットがかかるらしい。確かにかかった。上の ;
の補完もやってくれる。クォーテーションの統一もしてくれる。いい感じ。
1028.07.12 追記
Alt-Shift-Cmd-Pを押しても何もおこらないとき
prettierの実行ファイルへのパスを指定しないとダメみたい。↓にやり方が書いてある。
JavascriptのExportについて
前提
$ node --version v.9.11.1
背景
import React from 'react'; import {render} from 'react-dom';
前者のように{}無しで書く場合と後者のように{}有りで書く場合があって、よくわからなかったから調べる。
名前付きエクスポートとデフォルトエクスポート
エクスポートの方法によってインポートの方法も変わるようだ。
名前を付けてエクスポートする場合はインポート側もその名前で束縛しなければならない。デフォルトエクスポートの場合はインポート側で束縛する名前を指定できる。
以下はlib.jsでaとbを定義してindex.jsでaをxとして、bをbとしてインポートしている。
lib.js
const a = 'this is a'; const b = 'this is b'; export default a; export {b};
index.js
import x from './lib'; import {b} from './lib'; console.log(x); console.log(b);
参考
名前付きエクスポートとデフォルトエクスポートについてはMDNを良く読む。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export
nodeでimport/exportを使う方法
ついでにnodeでimport/export文を使うには .js
でなく .mjs
として保存し、 node --experimental-modules index.mjs
と実行する。
参考
適当にググってこのQiitaが出てきた。
https://qiita.com/ryohji/items/93f5050b9af6fc15693c
公式はこのへん。
dockerの-vオプション
なんかいつも忘れるからメモ
書き方
docker -v ホストのディレクトリ:コンテナのディレクトリ image名
カレントディレクトリをマウントしたいとき
docker -v $(pwd):/path/to/directory image
覚え方
前と後ろのどっちがホストのディレクトリだっけ?っていつも悩んでしまう。
Dockerは基本的に基本的にホスト:コンテナの順で書く。だから大抵source => destinationの順になってる。
-v $(pwd):/path/to/directory
だったらホストのカレントディレクトリをコンテナの /path/to/directory
にマウントする。
portもそう。-p 80:8080
だったらホストの80番をコンテナの8080にフォワードする。
展開のされかた
以下のようなディレクトリ構造がホストに構築されているとする
- /app/build/
- index.html
- style.css
で、-v /app/build:/usr/share/nginx/html
と書くとコンテナでは以下のように展開される。
- /usr/share/nginx/html/
- index.html
- style.css
以下のようにはならない。
- /usr/share/nginx/html/
- build/
- index.html
- style.css
- build/
逆に↑のようにしたければ以下のようにコンテナのパスの方に書くしか無い。
-v /app/build:/usr/share/nginx/html/build
またコンテナのパスのほうに存在しないディレクトリを書いても勝手に作ってくれる。
TAMRON 28-75mm F/2.8 Di III RXD
おれのα7にはオートフォーカスするレンズが着いたことが一度もない。初代α7が発売されてすぐに買ったから、もう4年間も使っていることになるが、全てフルマニュアルで撮ってきた。カメラマンだったらしい祖父の遺した古いNikkorとCanon FDレンズだけがレンズ資産だった。レンズがそんなだから、ピントも絞りもレンズを直接いじくって決めるし、「どうせなら」と思って測光もマニュアルでやっていた。撮影には手間と時間がかかるけど、それくらい時間をかけられるような余裕を持って被写体を眺めた方が発見があるんじゃないかなと思っている。
この桜の写真は AI Nikkor 50mm f/1.4Sで撮った。風が吹くとすごい量の花びらが舞うので、その様子を撮りたくて、1時間くらい待った。それくらいの時間感覚で写真とは向き合うのがおれは楽しいと思っている。
しかし、この時間のかけ方は趣味で一人でやるにはいいけど、人と旅行に行くときはそうもいかないので、やはりオートフォーカスするレンズが欲しいなあと去年頃から実は思っていた。それで、今年のCP+でTAMRONがEマウントのフルサイズ向けに標準ズームを出すと発表していたから、発売したら買おうと思っていた。
それがこれ。今日はこれを持って旧古河庭園に行った。
解像度が高い。おれは今までα7の本気を知らなかったんだなあと思った。洋館の壁面の写真なんか、構図も意識せずに適当にパシャッとやったんだけど、それなりの写真に見えてしまう。被写体の質感を正確に伝えるだけで表現になるというのは知らなかった。1枚目も3枚目も木々の葉の質感だけで訴えられる。
この下の桜の写真はさっきの桜の写真と同じレンズで撮ってるんだけど、上に比べるとまったく解像してなかったんだとわかる。撮ったときは結構撮れてるじゃんと思ったんだけどな。
とにかく解像度に驚いた。しかしなんでも解像すれば良いというものでもないだろうとも思う。表現の幅としてどれくらい解像させるか?という軸が自分の中に加わった。
信州大学工学部生に行って欲しい飲食店リスト(千石劇場周辺編)
今日は信州大学工学部に在学する後輩たちに長野市街地で行って欲しい飲食店を紹介します!
おれの長野市街地うまい店リスト文章にして公開しとくか
— ろにゃ (@roronya) 2018年5月4日
Twitterで適当なこといったら反応があったので書くことにした。おれは24年という長いあいだ長野に居たわけだが、去年とうとう就職で東京に引っ越してしまった。東京の生活も1年も経つと慣れてきて美味い店、居心地の良い店なんかも見つかってきたが、それでも思い出す長野の店についてここに書く。
あじ源
https://tabelog.com/nagano/A2001/A200101/20001074/tabelog.com
一店舗目だし、景気良く焼肉屋を紹介したい。蕪村と千石劇場の間にある老舗焼肉屋だ。良い肉は量を食べられないということをおれはこの店で知った。口コミや評価が食べログに全く無くてビビるだろうが、若者が行かない高級店はインターネットで正しく評価されにくいのだろうか。"高級店"と書いたが、前述の通り、良い肉は多くは食べられないのでひとつひとつのメニューは高く感じても5千円も飲み食いすれば「今日は贅沢しちゃったナ」という気持ちになる。インターネットに口コミは無くとも、ここは人気店なので予約は必須。おすすめ。東京でこの肉を食べようと思ったら5千円じゃすまないぞ。
かかし
https://tabelog.com/nagano/A2001/A200101/20006460/tabelog.com
あじ源を紹介したのだし、隣のかかしも紹介しよう。こっちは老舗串カツ屋だ。この店に来たら食べ放題をやってほしい。35本以上食べると「短冊に名前とメッセージを書け」と言われる。その短冊は店に飾られるので、店の中は壁から天井までびっしりと短冊が飾られている。おれはこの短冊に自分のサークル名と他人のTwitterアカウントを書いてきた。ぜひ見つけて欲しい。
— ろにゃ (@roronya) 2016年7月7日
35本はなかなかたいへんだった。食べ放題には飲み放題も付いているが酒を飲んでいる場合ではない。値段は4,950円で普通だ。体調を整えて挑戦して欲しい。予約は要らないだろう。普通にふらっと行けば入れる。店長の耳が遠くなっているので注文は大声でする必要がある。
三本コーヒーショップ
https://tabelog.com/nagano/A2001/A200101/20005282/tabelog.com
老舗の通りを攻めているので、また近場の老舗を紹介したい。"みつもと"コーヒーショップと読む。外観からして最高。入ると更に最高で、大抵の場合はウェイター制服の爺ちゃんがカウンターに座りタバコを更かして新聞紙を読みながら客を待っている。注文をとると科学の実験器具みたいな道具でコーヒーを落とす。ブレンドコーヒーが美味いが、カフェオレを頼むと楽しい。爺ちゃんが右手にミルク左手にコーヒーでビャーっと高いところからコップに落として作ってくれる。値段は普通だ。スタバと変わらない。
アルデンテ
https://tabelog.com/nagano/A2001/A200101/20000202/tabelog.com
また老舗か?と思っているだろうが、また老舗だ。40年以上ここで店をやっているらしい。パスタ麺が丸くて太いのが特徴で食べごたえがある。ナポリタンやポモドーロなど一般的なメニューもあるが、ここは和風のオリジナルパスタが数種類あって、おれはここのアサリしめじ納豆がすきだ。パスタに納豆入れると美味いということをここで知った。値段はラーメン一杯分くらいで普通だ。食後にはコーヒーが付いてくる。コックさんがいかにもな白いコック帽をかぶっていて最高。
うらん
https://tabelog.com/nagano/A2001/A200101/20008090/tabelog.com
なんとなく書いていたら千石劇場周辺に固まってしまった。最後にうらんも紹介しよう。店の名前にもなっているとおり卵料理がめちゃめちゃ美味しい。種類もたくさんあるので、前行ったときは卵ばかり食べてしまった。魚も美味い。地酒も種類がある。外から店の中を除くとカウンターしかなくてビビるだろうが二階に座敷もある。研究室の打ち上げとかでも使えるんじゃないかな。
疲れた。まだまだあるけど一旦千石劇場周辺編ということにしたい。反響があればまた書く。
「Ruby on Rails 5アプリケーションプログラミング」の環境をdocker-composeで作る。
背景
仕事でrailsを使うことになったがきちんと触ったことが無かったのでGWに試してみることにした。
railsの入門書はこれが良いらしいので買った。書籍ではローカルに環境を構築しているが、dockerを使ったほうが良いだろう。
Ruby on Rails 5アプリケーションプログラミング
- 作者: 山田祥寛
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/14
- メディア: 大型本
- この商品を含むブログを見る
docker-composeを用いたrailsの環境構築は公式にドキュメントが用意されているが、rubyやrailsのバージョンが本で解説されている動作環境とは異なるので、合わせた環境をdocker-composeで構築してみる。また、本だとDBにSQLiteを使っているが現実的にはMySQLのほうが良いのでここも変えたい。
クイックスタート・ガイド:Docker Compose と Rails — Docker-docs-ja 17.06.Beta ドキュメント
動作環境
本の動作環境はこのようになっている。
- CentOS 7.2.1511
- Ruby 2.4.0-p0
- Ruby on Rails 5.0.1
- SQLite 3.7.17
Docker HubのrubyのイメージはCentOSが無いので妥協する。DBはMySQLを使うことにする。
https://hub.docker.com/_/ruby/
Dockerfile
上にも貼ったdocker-composeの公式のドキュメントを見ながら本の動作環境にバージョンを揃える。
FROM ruby:2.4.0 RUN apt-get update -qq && apt-get install -y build-essential mysql-client nodejs RUN mkdir /myapp WORKDIR /myapp ADD Gemfile /myapp/Gemfile ADD Gemfile.lock /myapp/Gemfile.lock RUN bundle install ADD . /myapp
差分
メモ
apt-get update -qq
の-qq
はエラー以外表示しないオプション。- ダウンロードやインストールのログは大量に出るから、もしコケたときのデバッグがしにくくなるから表示しないようにしてるのかな。
Gemfile
source 'https://rubygems.org' gem 'rails', '5.0.1'
空のGemfile.lockもドキュメントと同様に用意する。
$ touch Gemfile.lock
差分
- railsのバージョンを4.2.0から5.0.1へ変更。
docker-compose.yml
version: '2' services: db: image: mysql environment: MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/myapp ports: - "3000:3000" depends_on: - db
差分
メモ
- Dockerfileで
Add . /myapp
しているから、docker-compose.ymlで改めてvolumes: .:/myapp
としなくてもいいんじゃ?と思ったが、そんなことなかった。- 前者は
bundle install
するためだけのファイルコピーで、後者はローカルのファイルの変更をコンテナに渡すために必要だった。
- 前者は
- depends_onディレクティブは「dbを先に立ち上げよ。」という意味になるようだ。ただし、コンテナの立ち上がる順序は指定できても、立ち上がりシーケンスが終了したかどうかは確認してくれないようだ。これによって起こる問題は後述する。
- MYSQL_ALLOW_EMPTY_PASSWORDは設定しないとMySQLが立ち上がってくれない。
- 最初はこれを環境変数に設定せずにいたら立ち上がらなかった。
docker logs
でログを確認したところこれを設定しろというメッセージが出ていた。
- 最初はこれを環境変数に設定せずにいたら立ち上がらなかった。
rails new
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
差分
- --databaseオプションで指定するデータベースをpostgresqlからmysqlへ変更。
メモ
- --force は上書き許可オプション。
- 参考: railsコマンド(rails) - - Railsドキュメント
- 冪等性の確保のためにつけてるのかな。
- --skip-bundleは bundle installを行わないオプション。
- bundle installはDockerfileでやるからここではスキップするのかな。
docker-compose build
ここはドキュメントと変わらない。
rails new したことによってrailsのファイルがローカルに振ってきて、Gemfileも更新されている。これを編集してJavascriptのランタイムを入るようにしてからdocker-compose buildする。
ここわかりづらかった。最初の docker-compose run web rails new . --force --database=postgresql --skip-bundle
ではrailsしかインストールされてないミニマルなコンテナがビルドされる。そしてこれはrails newするためだけのコンテナになる。二回目の dockee-compose build
でさっきrails newして降ってきたファイルも取り込んだコンテナが出来上がる。
差分
- なし
データベース接続
ドキュメント通り、docker-compose up
でrails serverが立ち上がる。ブラウザで http://localhost:3000
を確認すると、上手く行っていれば、 Unknown database 'myapp_development'
というエラーが表示される。また、ブラウザで直ぐに確認した場合は Unknown database servber 'db'
というエラーも見える。これは前述した立ち上げ処理の終了まで面倒を見てくれないため、MySQLの起動よりも先にrails serverが立ち上がってしまうことが原因。こっちが出た場合はしばらく待つと前者のエラーに切り替わるはず。
差分
- なし
データベース作成
ドキュメントどおり、 docker-compose run web rails db:create
とすることでデータベースが作成される。改めて docker-compose up
して http::/localhost:3000
にアクセスすると繋がるようになる。
差分
- なし
メモ
なんでdocker-compose runの結果が、改めてdocker-compose upしても保持されたままなの?
まず、docker-compose upしてCtrl-Cで止めた後、再度docker-compose upするとコンテナを作り直す。よって、docker-compose runの結果が保持されることは無いはず。しかし、コンテナにマウントされたボリュームは作り直さないようだ。
マウントしているボリュームは、そのまま保持します
引用元: up — Docker-docs-ja 17.06.Beta ドキュメント
ということなので、MySQLのイメージはどこかにボリュームを持っているらしいことが推測できる。それで調べるとやはり持っているようだ。
実はデフォルトで永続化されるように設定されています。
引用元: Dockerの公式MySQLイメージの使い方を徹底的に解説するよ · DQNEO起業日記
というわけなので docker-compose run web rails db:create
でデータベースが作成されるが、その変更はホスト側のボリュームに書き込まれ、改めて docker-compose up
でコンテナを作り直してもデータベースは作成されたままというわけだった。
データベースを永続化する。
この手順はドキュメントに無い。
以上までやっても docker-compose down
して再度 docker-compose up
すると、 Unknown database 'myapp_development'
というエラーが出る。上述の通り、デフォルトで永続化されてはいるので、そのパスをボリュームにすれば解消される。しかし、ホスト側からは見えづらいパスにボリュームがある(docker inspectするとパスがわかる)ので、明示的にマウントするボリュームは指定しておいた方が使い勝手が良い。
version: '2' services: db: image: mysql environment: MYSQL_ALLOW_EMPTY_PASSWORD: 'yes' volumes: - ./tmp/db:/var/lib/mysql web: build: . command: bundle exec rails s -p 3000 -b '0.0.0.0' volumes: - .:/myapp ports: - "3000:3000" depends_on: - db
差分
- ボリュームを./tmp/dbに指定した。
これで開発環境は整った。