はじめての水草植栽(0日目その3)
いよいよ水草を植えます。ADA BIOみずくさの森というシリーズで、ショート・ヘアーグラスとヘアーグラスを買ってきてます。
まずはじめにショート・ヘアーグラスから。プリンの入れ物みたいなのから出すと5cm径の草の塊でした。お店でも雑誌でも、ミスト式の場合は培養地を全部洗い流せっていうのですが、それっぽいものが無いんで戸惑います。
ハサミで切って小さくしたのをピンセットでつまんで水をくぐらせてみたのですが、葉がポロポロ落ちるばかりで培養地っていうようなものが見当たらない。今度も面倒になってきたので形ばかりは洗いながら植えて行きます。丈も根も短いので植えるというよりはソイルを凹ませてそこに置く感じ。
ショートが終わったので、次に普通のヘアーグラスを開けたらこちらはゼリーのような培養地がたっぷりと付いてました。こちらは洗っても内側からどんどん出てくる感じで、なかなか難儀。またまた面倒になって洗いが完璧ではないけど植えてしまう。こちらは丈も根もしっかりあるので、(やったことはありませんが)田植えのように。
ラップで密閉して完成。やっぱりソイルが厚いな。入れて濡らして草も植えちゃったから今回はこのままで行くけど、次は最低限の薄めに入れることにする。もしかしたら次は今のソイルの上半分を削り捨てるだけでもう一回いけるのかも。
あとはほっておくだけ。照明のオンオフも自動だから本当に放置。6週間を予定しています。。
ミスト式水槽のレイアウト(0日目その2)
いよいよ水槽のレイアウトと水草の植栽を始めます。雑誌でいろいろ読みましたが、全て初めてのことで何やっても勝手がわからない。が、まず水槽にソイルを入れる。袋を開けてドサドサと。
ここでちょっとおかしいなとは思ってたのですけどその時には違和感の原因に気がつかなかった。3リットルのソイル2袋を店で持たされたので2袋全部入れたのです。30cm x 30cmの底面ですから、単純計算で6.6cmの深さになります。少々起伏を作っても6cmでこれがなんか深いんだ。どうやらここまで深く入れる必要なく、残しておけばよかったみたい。でも私は入れちゃった。
石を入れてそれっぽくレイアウトする。石は大きな物を1つと小さいものを2つ、グラムで売ってたので選んだ石の重さを計ってお会計しました。雑誌の写真を参考に試行錯誤してみますが何がいいのかわからないからだんだんめんどくさくなってくる。自分の直感を信じてソイルに埋めてみた。心配だったのは石をガラスに直接置いてもいいのかと事前質問しておいたのだけど、ガラスは相当丈夫で直に石をおいても割れたりしないし弄ってるうちに結局はソイルの上に置く感じになりますよ、と聞いてた通りだった。
一応のレイアウトができたので水を入れます。はじめ霧吹きでやってたけどソイルを入れすぎたためかなかなか湿らないので、石に水をかけるようにして入れました。仕上げに各所を霧吹きで湿らせる。はじめのイメージと異なり結構水を使いました。
お店でもらった木っ端で1cmに満たないぐらい持ち上げて、パネルヒーター を差し込んでます。次女が爬虫類に興味を持ってるのでいずれこれはそちらに回ることになるのかも。
水槽の裏側に温度計シールを貼ります。部屋は寝てる間は暖房してないのですごく寒いのですがこれで水槽が最低20度ぐらいに保たれるようになりました。暖房かけてると27度ぐらいになります。
水槽の中のもの買い出し(0日目その1)
金曜の夜は営業系の忘年会で飲みすぎて、土曜の起床は遅い。昼過ぎにお店へ寝巻きジャージのままで買い出しに行く。
- 底床:デルフィス リベラソイル 3L x 2
- パネルヒーター :GEX レプタイルヒート S
- 温度計:水作 貼るテンプ M
- ピンセット:DOOA アクアピンセット L
- 水草:ADA BIOみずくさの森
- 飾り:なんかお店にあった石 4.5kg
この後、ちゃんとしたハサミがうちになかったので買い足し。
- ハサミ:DOOA アクアシザース S
パネルヒーター と温度計は初期のみ用途なので適当です。今回欲しい外部フィルターが欠品しているのでWEBで調べてたら最近の流行としてミスト式なる始め方があるとのことでそれで挑戦。初期段階では水槽に水を入れずに水草を水上葉のままで根付かせるところまで育てるという方法なので、要は水が入らない。今は冬。爬虫類用のパネルヒーター を用いて水槽を温めます。水が入ってないので水槽用のJ型温度計が使えないため、簡単な使い捨てシールタイプの温度計を買ってきました。
ここまではお買い物日記。これから水槽立ち上げ作業編になります。
アクアリウムの電気系を整える(マイナス1日目)
照明を毎日同じ時間で点灯&消灯するのが良いらしく、そのためにはタイマーを使って制御するのが普通らしい。アクアリウムの周辺品は通電コントロールだけでオンオフできるようになってるものばかりのよう。ここまでの前提で、この目的に限らず汎用に使えるものとしてIoTなスマートタップを探しました。Amazonに出てるのですが、製品のサイトは見つからない。大丈夫かと思いつつも評価も高かったので購入。ADAじゃなくてAMAポチ。
4口のものと3口のものがありましたが、照明とCO2電磁弁との二つだけがタイマーコントロール対象かと思いますので、3口のものにしてます。値段はそんなに変わらないけど無駄に口数なくて良いかなと思って。
これが結構想像以上に良いもので、アクアリウム用途に限らず面白い使い方いくらでもありそう。設定がまずスマホアプリ必須でアプリないと全く動かせない。アプリ入れたらWiFiなど簡単に設定。UIもシンプルで遠隔操作が可能な上に通電オンオフを曜日&時間で設定できます。今回はまず毎日10時にオン、毎日20時にオフという10時間照明がついているように設定しました。
このスクショとったあと、起きてる間に水槽見えなくなるのも残念なので消灯22時・12時間照明に変更してます。時間が来ると「カチッ」とタップが結構大きな物理音を響かせて電源のオンオフが切り替わるのがそれだけで楽しい。
スマホの画面ではタップの名前をつける設定があります。私は「AQUAZONE」にしました。昔そんな名前のPC向けシミュレーションゲームがあった。で、思い出してググって見ると今でもあるのですね。アロワナとか現実には相当難しそうな魚も飼えるゲームになってました。
水槽買ってきた(マイナス2日目)
土曜にアクアリウムをスタートするにあたって、まず水槽と照明を買ってきました。店が近いから帰宅後に夕飯食ってさあ行くか!と。次女がヒョウモントカゲモドキを見たくてついてきます。改めて聞いたらカメレオンにも興味があるらしい。
水槽の大きさは小さければ小さい方がよく、15cmぐらいを理想としたのだけどお店で相談したら小さいのはかえって難しいし、あらかじめ伝えてたやりたい方向と違うということで見かけも可愛らしい30cmキューブにしました。やりたい方向としてはより本格的な大型水槽にもスケールアップできるノウハウ積める構成を小型水槽でまずやってみるということです。小さすぎると大型水槽で必須となる水濾過フィルターやCO2添加システムとかが組み込めなくなる。30cmキューブならば27リットル容量なのでそれなりに構成できるとのことで決定。
照明はADAのセカンドブランド(?)のDOOAカタログでこれに決めてた。水槽上に設置するものではなく、シルバーのパイプですっきりかっこいいので。1点で支えているのにも関わらずちゃんと照明本体が水平になり、高さも簡単に上下できてよくできてると思います。電源ケーブルはApple Macbookみたいにマグネットでくっついていてケーブルを万が一に引っ掛けてもダメージリスクが少ないようになってる。
初アクアリウムからガチ投資。家族の応援ムードがあるのでお金かけても批判の心配ない。
置き場所は初めから自室に決めてたので、机の上を模様替えしながらあれこれ設置場所を試行錯誤。部屋は北向きで直射日光入らないので窓際でもいいかと。照明スタンドの柱や外部フィルタのパイプなどが全て水槽の背面で壁際に這う感じ。メインの視線は椅子に座って斜めに眺めるのと、机の向こう側がベットなので寝転んで横を見あげたら水槽あるというのがサブの視線。部屋のメインのペンダントライトがルイスポールセンPH5で、それ以外も暖色だったり間接照明だったりを複数足していただけの部屋でいきなり主張する白色の強い光が現れて、その点には違和感がある。将来飽きたら部屋の隅とか部屋の外とかに移動することになると予想。
外部フィルターは狙ってるのが品薄で入ってきてないとのことなので待ちます。
- 予定:外部フィルター: ADA スーパージェットフィルター ES-150 Ver.2
- 予定:クーラー&ヒーター:ゼンスイ 小型ペルチェ式クーラー TEGARU
- 予定:CO2添加:はるデザイン CO2ジェネレーター PRO-D601s
お店の人の話ではES-300とES-150に新機種でたあとモーターに不具合が見つかってメーカー対策中ならしい。フィルターは発注かけといて私の必要時に入荷が間に合わなかったらお店で余ってるのを貸してくれるって。便宜が得られるように家族揃って太客になりそうなアピールをしていた成果が早くも出てきたかw
アクアリウムを始めます(マイナス3日目)
突然ですがアクアリウムを始めます。始めるのは突然ですが思いたったのは7年〜6年前に駒場に住んでた時から。家の斜め前にアクアリウムショップがあってよく通りかかるうちに興味をもち、カタログもらったり、雑誌買ったりして妄想膨らませていました。
当時わからなかったけど今わかるのは、そのアクアリウムショップはアクアデザインアマノ(ADA)の特約店だったのですね。もらったカタログも買った雑誌も全部ADAの出版物だった。雑誌の解説の通りにモノを揃えてくと結構な金額になるなーと思いながらためらってるうちに渡米することとなりました。そこで一旦はアクアリウム熱もおさまり忘れてました。
今回のきっかけは妻からです。家から徒歩1分ぐらいのところにアクアリウムショップがあって(やはりADAの特約店だった)、ウチのゴールデンレトリバー牡4歳が毎朝の散歩の途中でショップ店長に撫でてもらうのが日課になってました。撫でてもらってる最中に雑談でアクアリム知識を仕入れてきて、妻がアクアリムを始めたくなっちゃった。駒場の時には子育て忙しいわ私は仕事だと言い張って家に帰らねーわで落ち着いて何かする考えでなかったのが、娘達も大きくなって犬も飼って、私も家庭的になって。そうなると「いや、俺はずっとやりたかったし」と私も主張して雑誌も引っ張り出してきて。今後はお父さん水槽でノウハウを蓄積した上でお母さん水槽やお姉ちゃんコケリウム、さらには妹ヒョウモントカゲモドキゲージが現れることになりそうです。
よってこのブログは、しばらくアクアリウム多めでTypeScriptやITビジネス少なめな構成になります。最近手元で面白いライブラリを書いていてTypeScriptやReactもお知らせしたいテクニックがたまってきているのだけど、脳の大半がアクアリムになっちゃってるのだからしょうがない。
TypeScriptのプロジェクトでTSLintからESLintへ移行する
昨年夏ぐらいから、TSLintは終わらせてESLintでTypeScriptプロジェクトも対応していこう、という世界的な流れになってました。「脱TSLint」とか「ESLintでTypeScript」とかググればその辺の記事が出てきます。当時早速私も乗っとこうとESLintに移行しようとしたのですが、対応レシピが複雑で未成熟なことからすぐに諦めて延期していました。今回、手元で新しいプロジェクトを始めるにあたってESLintを試してみたのですがスッキリいい感じ。機は熟した、ESLintでOK。
ESLintとTypeScriptのセットアップ
{ "scripts": { "lint": "eslint src --ext .ts,.tsx" }, "eslintConfig": { "extends": ["airbnb", "plugin:@typescript-eslint/recommended"], "plugins": ["@typescript-eslint"], "parser": "@typescript-eslint/parser", }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/parser": "^2.10.0", "eslint": "^6.7.2", "eslint-config-airbnb": "^18.0.1", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.17.0", "eslint-plugin-react-hooks": "^2.3.0", "typescript": "^3.7.2" } }
devDependenciesにある一連のものをインストールしますが、前提としてはTypeScriptでReactアプリを書いてます。基本は厳しいことで無条件に信奉しているAirBnBにTypeScriptの手当を最低限だけ。プロジェクトはJSの混入無し前提。全てTSとTSXで書いてます。
ここから先は settings と rules をこまめに調整しています。
Unable to resolve path to module 対策
まず必要だったのはimport文が「Unable to resolve path to module」と全滅なところ。これは無設定ではJSしかimport先を追いかけないためで、以下を追加しています。
{ "eslintConfig": { "settings": { "import/resolver": { "node": { "extensions": [".js", ".json", ".ts", ".tsx"] } } } } }
これは、みたまま通りimport文でソースファイルの拡張子が省略されているところを補完する候補を増やすもの。TSとTSXが必要で、その上でnode_modulesの下にインストールされているものを読み込むためにJSが必要。あとはJsonModuleを有効にしている際にはJSONも加えます。よってこのようになりました。
Expected a line break after this opening brace 対策
以下のようなコードで問題があったのを記述で解決します。
<Button icon={props.icon} onClick={() => props.dispatch(props.action)}>OK</Button> // Must use destructuring props assignmenteslint(react/destructuring-assignment)
Reactのプロパティを展開する際に、props.* のように使ってしまうと react/destructuring-assignment に引っかかる。ならばと、以下のように書き直します。
const { icon, dispatch, action } = props; <Button icon={icon} onClick={() => dispatch(action)}>OK</Button>
これで「Must use destructuring props assignmenteslint」とは言われなくなりますが、今度は「Expected a line break after this opening brace」と言われるようになる。プロパティをオブジェクトに展開しているところで、1行で書いちゃだめだと。。。改行入れればいいだけなのですが、どうにも縦長になってしまうので object-curly-newline はオフにしました。該当箇所は以下の通り。
{ "eslintConfig": { "rules": { "object-curly-newline": ["off"] } } }
TSXへの対応
無設定では、JSXタグが、拡張子JSXのファイルでだけ許すという設定になっています。しかし私はTSXオンリーなことにしましたので、以下の手当。
{ "eslintConfig": { "rules": { "react/jsx-filename-extension": ["error", { "extensions": [".tsx"] }] } } }
一般的には、[".jsx, ".tsx"]とJSXも残しておくのでしょうね。私には不要でしたが。
Reactコンポーネントプロパティの型問題
JSで書く際には型が無いので、Reactコンポーネントのプロパティについては prop-types ライブラリを用いていちいちプロパティとして受け入れる値の定義を示します。しかしTypeScriptで書くとこれがいらない。コンポーネントプロパティについてがっちりTypeScript型システムでガードされるのでAirBnBは許さずとも、私は検出をオフにします。
{ "eslintConfig": { "rules": { "react/prop-types": ["off"] } } }
devDependencies問題
私は、テストやビルドに用いるための外部ライブラリは全て devDependencies に置くようにしてますが、テストで用いるEnzymeやStoryBook関連のライブラリは手当しないとdevDependenciesではなくdependenciesに置くよう「'enzyme' should be listed in the project's dependencies, not devDependencies」などと言われます。テストとStoryBookはパスパターンで除外しておきます。
{ "eslintConfig": { "rules": { "import/no-extraneous-dependencies": ["error", { "devDependencies": ["**/*.test.ts*", "src/stories/**/*"] }] } } }
私は上記だけにしましたが、あとは **/*.spec.ts*
も必要でしょうかね。末尾 .ts*
としているのは、テストにTSだけでなく、Enzymeで書くTSXがあるからです。正規表現使えないのでこうなりました。。。と、もしかして正規表現書けるのかな?わからず。
書き癖で直したく無いもの
{ "eslintConfig": { "rules": { "@typescript-eslint/explicit-function-return-type": ["off"], "react/jsx-props-no-spreading": ["off"] } } }
色々考えましたが、@typescript-eslint/explicit-function-return-type と react/jsx-props-no-spreading は自分の書き癖でも直したくなかったのでOFF。
// イベントハンドラでも関数戻値の型を書く?次の行では「:void」をあえて書いてみましたが… <Button icon={icon} onClick={(): void => dispatch(action)}>OK</Button> // 高階関数や、カリー化した関数では戻値の型を書くのスペース的に難しい const combineReducer = <S, P>(reducers: ReducerFactory<S, P>[]) => (initialState: S) => { const combineded = R.reduce<Reducer<S, P>, Reducer<S, P>>( (previous, current) => (state, action) => current(previous(state, action), action), (state) => state, R.map((r) => r(initialState), reducers), ); return React.useReducer(combineded, initialState); };
まず、@typescript-eslint/explicit-function-return-type の方ですが上記の2つの例のようにイベントハンドラ・高階関数・カリー化した関数では戻値の型を書くのがスペース的に美しく無い。またこれ以外にも関数戻値をTypeScriptの型推論エンジンに任せなければならない稀有な場合もある。よって戻値は型推論エンジンにおか任せするのが吉と思います。おかしなコード書いたらちゃんとトランスパイル通らないし。
<Component {...state} /> // もしくは <Component {...{ prop1, prop2, prop3, prop4, prop5 }} />
Reactコンポーネントにたくさんの属性引き渡さないといけない時に、どうしてもこういうオブジェクトスプレッド記法で書きたいです。そうしないとここに5個も10個も書かねばならない時もある。上記例での後の方の書き方だけ許すようなルールのオプションもあるのですが、どのみちめんどくさいので、 react/jsx-props-no-spreading はオフ。
グローバルオブジェクトの対応
{ "eslintConfig": { "env": { "browser": true, "jest": true } } }
宣言無しに用いるグローバルオブジェクトでLintエラーを出さないために、env設定を行います。私の場合はJestを用いることでdescribeやitなど、domを直接触るようなコードを書く場合には、windowやdocumentのために、それぞれbrowser: trueやjest:trueを設定します。
Fragmentの書き方
AirBnBのデフォルトでは、React.Fragmentの書き方が省略記法を推奨されてます。
<>{ /*ReactElementの配列など*/ }</> // 以下はできない <key={key}>{ /*ReactElementの配列など*/ }</>
これはさっぱりした見かけで私は不慣れながら書いてりゃ見慣れるのですけど、keyを設定したりができない。
<React.Fragment key={key}>{ /*ReactElementの配列など*/ }</React.Fragment>
省略せずにFragmentを書く方が良いかな。react/jsx-fragments で element をオプション指定。
{ "rules": { "react/jsx-fragments": ["error", "element"] } } }
結果
もともとTSLintでAirBnB用いて書いてたから、書き癖を重んじてもルールもいじるところ少なくてスムーズに導入できました。ESLintへの移行部分としてはプラグイン設定するだけなので難しいことがない。自分ルールとしてインデントは全部スペース4つと決めています。
完成は以下の通り。ただしESLintに関係するところだけです。
{ "scripts": { "lint": "eslint src --ext .ts,.tsx" }, "eslintConfig": { "extends": ["airbnb", "plugin:@typescript-eslint/recommended"], "plugins": ["@typescript-eslint"], "parser": "@typescript-eslint/parser", "env": { "browser": true, "jest": true }, "settings": { "import/resolver": { "node": { "extensions": [".js", ".json", ".ts", ".tsx"] } } }, "rules": { "@typescript-eslint/explicit-function-return-type": ["off"], "import/no-extraneous-dependencies": ["error", { "devDependencies": ["**/*.test.ts*", "src/stories/**/*"] }], "indent": ["error",4], "object-curly-newline": ["off"], "react/jsx-filename-extension": ["error", { "extensions": [".tsx"] }], "react/jsx-fragments": ["error", "element"], "react/jsx-indent": ["error", 4], "react/jsx-indent-props": ["error", 4], "react/jsx-props-no-spreading": ["off"], "react/prop-types": ["off"] } }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^2.10.0", "@typescript-eslint/parser": "^2.10.0", "eslint": "^6.7.2", "eslint-config-airbnb": "^18.0.1", "eslint-plugin-import": "^2.18.2", "eslint-plugin-jsx-a11y": "^6.2.3", "eslint-plugin-react": "^7.17.0", "eslint-plugin-react-hooks": "^2.3.0", "typescript": "^3.7.2" } }