技術書典6で「jQuery to Vue.jsで学ぶ レガシーフロントエンド安全改善ガイド」という本を出します #技術書典

1章のみを無料サンプルとして公開しています。

タイトルのとおり、4/14の技術書典6で「jQuery to Vue.jsで学ぶ レガシーフロントエンド安全改善ガイド」という本を頒布します。

技術同人誌というものを初めて書いていますが、せっかくなら一人でも多くの方に見てもらえると嬉しいので、宣伝させてください。

f:id:mugi1:20190312232505p:plain

表紙イラストは鍋料理さん(@yaminaberyouri)に描いていただきました!めっちゃステキ。

目次

  • 1-4章 : 改善のための心構えやレガシーコードの理解、着手前の準備について
  • 5-7章 : サンプルコードをjQuery to Vue.jsに実際に書き換えていく具体例
  • 8章 : コード以外の面で改善をうまく進めていくための話

のような構成になっています。 (なお現在校正作業中なので、内容は多少変わる可能性があります。)

f:id:mugi1:20190312234156p:plain:w400 f:id:mugi1:20190312234207p:plain:w400 f:id:mugi1:20190312234218p:plain:w400

どんな内容?

表紙はとってもカワイイですが、私がここ2年ほどやってきたフロントエンドの改善経験がベースになっているため、中身はゴリゴリに現実的な話になっています。

jQuery to Vue.js とありますが、内容の本質は「安全なレガシーコードの改善」です。 安全とはデグレを減らすといった意味合いもありますが、何よりも心理的安全をどう確保しながらリファクタリングを進めるかについて、心構えや準備に加え、テストコード・コード分割・レビュー戦略など、様々な観点からのアプローチを紹介しています。

そして、jQuery to Vue.jsを実際に書き換える流れを段階的に追っていくことで実践的に学べるようになっています。

Vue.jsに限らず、流行りのフレームワーク・ライブラリを導入しようとしたときに、ゼロベースでの構築方法の情報はたくさん見つけることができますが、稼働しているリアルな現場に投入するための情報というのはあまり存在せず、実際やろうと思うとかなり苦労します。

というより、実際私がめっちゃ苦労した(している)ので、その経験から、どのようにやれば心臓をバクバクさせながらの恐怖のリリースをせずに済むのかをまとめています。

  • レガシーなフロントエンドコードに困っている・困りそう
  • フロントエンドのリファクタリングに興味がある
  • 改善作業についてのノウハウを知りたい
  • Vue.jsを導入しようと思っているがどうしたらいいかわからない
  • Vue.jsを手を動かしながら学ぶチュートリアルが欲しい

といった方にオススメです。

頒布数

現在悩み中です。100-150くらいを予定していますが、様子を見て増減したいと思います。


というわけで、よかったら当日買ってもらえると嬉しいです!

/ @mugi_uno

※なお、一瞬だけマルチカーソルの本を書こうかと思ったのは秘密です。

軽減税率制度について思うこと

ブリ会議2019の懇親会で、酔った勢いでToyama.rbの年末LT大会でやった軽減税率の話を再演した。

法律施行の10月まで使える鉄板ギャグみたいな扱いをしていて、軽減税率芸人になりつつある。が、正直なところ本当にこれはギャグみたいな話じゃないの?って思っているのも事実。

なんだかな〜?と思っている点がいくつかあるので、思考整理も含めてまとめてみたい。

注意点

私は法律の専門家でもなんでもない。ただの一般人のプログラマとしての視点で考えたときに、おかしいな〜、って思った部分を書いている。そのため、その筋のプロが見たら「それは違う」という点があるかもしれない可能性も理解している。けど、これは個人のブログなのでそういうもんだと思ってほしい。

軽減税率とは

特集-消費税の軽減税率制度 | 政府広報オンライン によると、

社会保障と税の一体改革の下、消費税率引上げに伴い、低所得者に配慮する観点から、「酒類・外食を除く飲食料品」と「定期購読契約が締結された週2回以上発行される新聞」を対象に消費税の「軽減税率制度」が実施されることになりました。

とのこと。これを前提として書いている。

対象外品目の判断基準が複雑

ニュースとかでも言われているので知ってる人も多いと思うが、対象品目の判断基準が非常にややこしい。対象外品目として挙げられているのが

だが、ケースバイケースで判断しないといけないものが非常に多い。

実際に、個別事例のQ&A資料が公開されており、80以上のケースがひとつずつ記載されている。

消費税の軽減税率制度に関するQ&A(個別事例編)|国税庁

施行前の段階ですでにこれだけのパターンが出てきているのであれば、施行後にはもっと混乱を招くのではないだろうかと思ってる。

ちなみに個人的に一番面白いと思ったのは、「映画館のポップコーンが外食かどうか?」というやつ。答えとしては

  • 基本的には8%(軽減税率対象)
  • だが、売り場の前に座席などを設けた場合には対象外になる可能性がある
    • 飲食させるためのスペースとして設けた場合には軽減税率対象外(10%)

こんなもんわかるかよって感じである。

酒類・外食などの対象外品目に該当するか?ばかり考慮されていることにそもそも違和感がある

上記の個別例の資料では、内容の殆どが「これは酒類か?」「これは外食か?」といった内容のQ&Aとなっている。

あらかじめ対象外品目の種類を設定したからだと思うが、そもそも軽減税率は大前提として低所得者に配慮する観点から」実施されるものだったと理解している。

実施するのであればそこが一番重要で、様々な判断基準の中心にならないといけないような気がしているのだが、結果としては誰が見ても「???」な判断になってしまうものが多い。

パッと思いつくようなものでも、

  • 水道水 = 飲食専用じゃないから10%(軽減税率対象外)
  • ワンコインでお店で食べる牛丼 = 外食なので10%(軽減税率対象外)
  • かぜ薬 = 10%(軽減税率対象外)
  • 超高級な食材や金箔をリッチに使ったお弁当をテイクアウト = 8%(軽減税率対象)

などがある。

低所得者に配慮する観点から」という前提で考えると明らかにおかしいのは誰が見てもわかるが、最初に「酒類・医薬品・外食・ケータリング」といった大前提を決めたうえでそこに該当するか?という判断基準ですべてを考えてしまっているため、明らかに無理が出ている。

そもそも飲食料品が軽減税率対象で8%になるが、所得が多ければそれだけ飲食料品にかけるコストも高いことが多いだろうし、それらも一律で8%になってしまうのであれば、果たして誰のための軽減なんだろう?と思ってしまう。

新聞が軽減税率対象である話

これはもうなんというか、アレですよね。

言うまでもない。

適格請求書における免税事業者の負担の話

軽減税率が施行されるのと同日に適格請求書というものが導入される。 コレ自体に関する詳しいところはここでは触れない。

適格請求書等保存方式の導入について|国税庁

だが、

  • 「適格請求書発行事業者」でなければこれは発行できない
  • 課税事業者でなければ「適格請求書発行事業者」にはなれない

という点だけは気になっていて、現状では売上が1000万以下の事業者は消費税納税が免除されるが、上記の通り、課税事業者でなければ適格請求書というものは発行できない。

これの何が問題なんだろう?という話だが、請求書を受け取った側が消費税を納税する際、仕入れにかかったぶんは控除出来るが、これが適格請求書であることが条件になる。

https://www.nta.go.jp/taxes/shiraberu/zeimokubetsu/shohi/keigenzeiritsu/pdf/300416.pdf

大企業などが従来の免税事業者から従来の請求書を受け取った場合、仕入れ分を控除できないので、本来より大きな消費税を負担して納税しないといけない、といった状態が発生する。

これは完全に損なので、免税事業者側から見ると「あっ、適格請求書発行できないなら取引やめますね」って言われて契約が終わってしまったりするかもしれない。

これはあきらかにアレなので、免税事業者も申請をすることで適格請求書発行事業者になることができるが、それはすなわち課税事業者になるということなので、今度は消費税の納税義務が発生する。

つまり、

  • 適格請求書を発行できないためのリスクを受け入れたまま免税事業者で居続ける
  • 今まで不要だった消費税の納税を行って課税事業者になる

のどちらかを迫られることになる。つらい。

結局、中小企業などが損をする形になるように見えるので、これでいいのか?と思ってしまう。

結局の所

軽減税率制度の根底にある思想とかそのものは理解できるが、現状ではなんのためにこれを実施するのか?というのがブレブレに感じるし、そのために皆が多大なるコストを払わないといけないのは割に合わない。

世の中を無駄に複雑にするのはやめてほしいな、という思いが強い。「無駄」というのが問題で、もちろんすべての事象が1か0かで表現できるとは思っていないが、1か0かに出来るものはそちらに倒したほうが、そこにかかるコストを他に使えて有益じゃないだろうか。

今の形では本来の目的である低所得者への配慮という点が満たせないだろうし、いっそのこと一律10%とかに上げてしまったほうがわかりやすいし皆幸せじゃないかなと思ってる。(それはそれでどうなんだっていう話もあるんだろうけど・・)

なおエンジニア的な話をすると、システムにおける改修コストもべらぼうに高いと思う。消費税率が5%から8%になったのとかとは違う次元。サマータイムの件も思い出すが、基本的にITわからない人たちが色々決めているんだろうな、と思うと色々と不安しかない。

以上。

なお

これを書いたのは、サマータイムなどと違って自分の仕事に直撃する人の割合が小さめなので、あんまり騒がれてはいないように感じているため、問題意識を世の中のエンジニアに一人でも共有しておきたいな、って思ったのがある。

もし意識せずマズいこと書いてて、どこかに怒られたら消します。

終わり。

マルチカーソルを使わないVSCodeはただのVSCodeだ!〜解説編〜

先日投稿した以下のエントリで、「使い方がわからない」という意見を多く頂いた。

mugi1.hateblo.jp

マルチカーソル自体の操作方法は調べれば出てくるし、事例だけ紹介しとけばええやろ、と思っていたのだが、いきなり応用のサンプルを貼りすぎてわけがわからなかったらしい。申し訳ない。

せっかくなので、基礎から含め、どういったキー入力で上記のような操作を実現しているのかを紹介したいと思う。

🔥実践!マルチカーソル / 入門編

なおmac環境です。Windowsやその他環境の方は気合で調べてください。

また、言い訳臭くて申し訳ないが、私は普段はSublime Text Keymap and Settings Importerを使っており、SublimeTextっぽいキーバインドに変えて編集している。

一旦無効にしたうえでVSCodeデフォルトの状態で一通り調べて書いたつもりだが、もし違ってるところがあったらごめん。

📝 マルチカーソルの前に...

マルチカーソルの動作例を見てるとまるで魔法のように見えるかもしれないが、結局のところ「マルチカーソルはカーソルが複数になっただけ」ということを意識するといい。

つまり、通常編集時に単一のカーソルに対して行えることはだいたいできる。正直、これが全てと言っても過言ではなく、このあとに出てくる操作は全部これを応用しただけだったりする。

特に

  • 行頭と行末への移動・選択
  • 単語単位の移動・選択

は良く使うので、そもそもこれらの操作に慣れていない場合は、まずそこから始めるといい。

f:id:mugi1:20181211194306g:plain
いろんなカーソル移動・選択

💪 基本 / マルチカーソルのつくりかた

作り方はいくつかある。状況と経験と勘で選ぶ。

選択したものと同じものを1つずつ選択する

f:id:mugi1:20181211194934g:plain
command + D で選択後にカーソルを移動・編集

command + D で選択されている文字列と同じものを片っ端から選択状態にすることが出来る。その状態からカーソル操作を行うと、全カーソルを選択位置を基準に動かせる。

選択したものと同じものを一気に選択する

f:id:mugi1:20181211195807g:plain
一気に全部選択する

command + shift + L で 一発で全部選択することもできる。

ちなみにcommand + D のときと同じだが、文字列を選択していない状態の場合はカーソル位置にある単語をそれっぽく抜いてくれる。

クリックで任意の位置にカーソルを作る

f:id:mugi1:20181211195416g:plain
マウスでぽちぽちカーソルを作る

optionを押しながらクリックするだけ。

なお、設定で editor.multiCursorModifier いじることで修飾子を変えることが出来る。私はcommand + D と同様にcommandで操作したいので、"editor.multiCursorModifier": "ctrlCmd" という設定にしている。

マウスとかトラックパッドでポチポチやっても作れる。 文字列に何の規則も無いようなところにカーソルを作るときはこちらを使う。

範囲選択の各行の末尾をカーソル化する

f:id:mugi1:20181211201007g:plain
選択範囲の各行の末尾にカーソルを作って編集する

これが最強。とにかくcommand + D とこれだけを覚えると良い。かなり応用が効く。

デフォルトだとおそらく option + shift + I でいける。

現在のカーソル位置から上下行にカーソルを作る

f:id:mugi1:20181211201634g:plain
現在のカーソル位置から下にカーソルを増やす

command + option + ↑ or ↓ でいける。

現在の位置から上下にカーソルを増やしていくこともできる。 ただ、私はあんまり使ってない。これも使いこなすと便利だと思う。


ここまでの内容を見てもらうと既にわかるかもしれないが、結局はあとはこれらを応用して編集しているだけで、たいしたことはやってない。

🔥実践!マルチカーソル / 応用編

というわけで、上記を踏まえた上で、前回のエントリで貼ったサンプルはどのように操作しているかを紹介していく。なお、他の例と操作がほぼかぶっているものや、まあ見ればわかるでしょ、というものは省略する。

JSONで書かれたキーを定数に定義

f:id:mugi1:20181211203126g:plain

command + Dで連打したあとに選択範囲を変えてコピーして貼ってるだけ。

JSONyamlなどをマルチカーソルで取り扱うには、一定の規則で入力されているものを見つけ出すのがコツだと思う。慣れると息を吸う用にできる。

上記の例の場合には、JSONのキーを抜きたいのだが、キーの後ろには大概 :':": があるので、そこを対象にマルチカーソルで選択し、選択範囲を手前側に倒すことで一気にコピーしている。

貼り付けたあとは、範囲選択から各行末尾にカーソルを作りカンマを入力している。

他のサンプルにあった、「クォートで囲まれた中身だけ抜き出す」もほぼ同じようなことをやってるだけ。

https://cdn-ak.f.st-hatena.com/images/fotolife/m/mugi1/20181209/20181209173437.gif

これも結局、it(' を command + D で選択したあとに範囲を変更してるだけなのが理解出来ると思う。

JSONの整形

f:id:mugi1:20181211210956g:plain

これもどうということはない。

  1. 最初に全行にカーソルを作成
  2. 行頭に持ってくる
  3. deleteキーで改行を削除

とすることで一行に整形している。複数行に戻す場合には、

  1. 要素の区切りの , を選択
  2. Enterキーを叩く

とすることで一気に改行できる。

行の追加・削除的なことと組み合わせだすと混乱しがちだが、使いこなせるととても便利。

クエリを書くとき(選択範囲のマージについて)

「クエリを書くとき」は、ほぼここまでの応用なので省略したいが、一点特徴的な挙動があり、マルチカーソルは選択範囲が重なると合体して1つになる。

f:id:mugi1:20181211212315g:plain
選択範囲が合体してカーソルが1つに戻る様子

これを利用することでこんなことが可能になる。

f:id:mugi1:20181211212819g:plain
CSVから1列目だけ残してあとは消す

前エントリの、テーブル定義的なものをペーストしたあとに論理名だけ残るように削除しているのはこれを利用している。

いい感じに全部のケース変換をする

f:id:mugi1:20181211214545g:plain
一気にスネークケースに変換する

command + shift + P でコマンドパレットを開けるが、これをマルチカーソル状態から開いているだけ。あとはここまでの応用でできる。


ざっくりこんな感じ。

あとは「全行にデバッグログを突っ込む」が見た目としては派手な感じがあるが、ここまでのを見てくれたのであれば、それほど難しいことはやってないのはわかると思う。

まとめ

というわけで、ざっとコマンド付きのGifを撮り直して一通り説明を書いてみた。(つかれた..)

他のエディタでもマルチカーソルが使えるのであれば似たようなことが可能で、微妙に違いはあれども操作の勘所とかも同じはず。

あとは自分で調べたり試したりしてマルチカーソルライフをエンジョイしてほしい。

最後に、わりと書くの頑張ったので良かったらTwitterフォローしてやってください。 twitter.com

おわり。

マルチカーソルを使わないVSCodeはただのVSCodeだ!

Misoca+弥生+ALTOA Advent Calendar 2018の10日目のエントリです。

グッと来るタイトルにしようと思った結果、意味不明になってしまったのは自覚している。許してほしい。

※解説編について

何やってるかわからんという声を多数頂いたため、解説編を書いた。
よかったら併せてご覧ください。
マルチカーソルを使わないVSCodeはただのVSCodeだ!〜解説編〜 - memo.md

🤔 マルチカーソル?

さて、VSCodeではカーソルを複数作ることができる。

vscode-doc-jp.github.io

f:id:mugi1:20181207143102g:plain
簡単な動作例

これはVSCodeに限った機能ではなく、SublimeText, Atom, JetBrains製IDEなどでも似たようなことができる。

昔にSublimeTextを使い始めたころから愛用している機能で、私はこれが無いと生きていけない体になっている。

意外と使ってる人いないことに気付く

普段から仕事でもペアプロ・モブプロをすることが多いが、対応しているエディタ・IDEを利用していても、マルチカーソルをゴリゴリ使う人はあまりいないような印象がある。

いつ使えば良いかがわからない?

そういう機能があるのは知ってるし、なんか便利そうなのも知ってるけど、結局どういうときに使えば良いのかが良くわからない、という人がいるかもしれない、と勝手に予想した。

もったいない!

マルチカーソルは非常に強力な機能で、これを使いこなすかどうかでVSCodeの編集速度は劇的に変わると思っている。せっかくVSCodeを使っているのであれば、マルチカーソルもぜひ使いこなして快適なコーディングを楽しんでほしい。

そこで、キー操作などの使い方は調べれば腐るほど出てくるので、「私はこういう時にマルチカーソルを使ってるよ!」というのを紹介したい。

なお、「これがベストな方法だ」ということを言いたいわけでは決して無い。普通に他の機能で代替出来るものもあるし、無駄に思えるものもあるかもしれない。あくまでも参考として受け止めてほしい。

🔥 実例!マルチカーソル

ちなみに私はVim/Emacs両方共のキーバインドを利用していないのでご了承ください。

Vimも一時期使っていたが、キーリピート速度を限界まで高める&マウスを素早く操作するという強引なスタイルのほうが、スマートではないのはわかりつつも自分には合ってた。

検索からの置換

これが一番多く、かつ強力で便利。

f:id:mugi1:20181207143756g:plain
let → const に置き換える例

なお、言うまでもないが普通にエディタやCLIから検索&置換でも同じことは出来る。

検索欄に入力やコピペしなくても良いので、1ステップ分速いのが良くて使うことが多い。

ちなみに、CMD+dを連打して1個ずつ選択しているが、置換対象が適切かどうかの目視での確認も兼ねている。一気に全て選択するショートカットを使うときもある。

JSONで書かれたキーを定数に定義

JSONのフォーマットはわかっていて、それを全部変数に入れたい、みたいなケース。たまにある。

f:id:mugi1:20181207175252g:plain
JSONのキーを一気に抜く

クォートで囲まれた中身だけ抜き出す

たとえば、テストコードからitとして書かれている文字列のみ抜き取りたい、みたいなケースを考えてみてほしい。

愚直に1件ずつやろうとすると結構めんどくさいはず。

f:id:mugi1:20181209173437g:plain
itの名称のみを抜き出す

JSONの整形

何かが一定の規則で羅列されている場合に、それを横並びから一行ずつ、あるいはその逆へ変換するのが非常に強い。

たとえば、JSONの一部だけを横並びで書きたい場合などは便利。

f:id:mugi1:20181207180512g:plain
複数行のJSONを単一行にする。その後複数行に再度戻す。

クエリを書くとき

テーブルの定義は何らかの別の形式で記載されていることが大半だと思う。可能性としては色々ある。

  • schema.rb
  • 社内のナレッジベース
  • テーブル定義書ver2(これが最新です).xlsxという最強の資料

などだろうか。

雑にコピー&ペーストしたところからフィールド名だけぶっこ抜いてクエリにする、みたいなときにササッとやれる。

f:id:mugi1:20181209201950g:plain
DB定義のようなものからカラム名を抜いてクエリを作る

最近だと、大量のIDはわかっていてそれをIN句に放り込んでクエリを投げたい、みたいな時にも便利だった。

f:id:mugi1:20181207174456g:plain
大量のIDをIN句に含むクエリを書く

配列の定義を書く

とにかくある一定のルールで列挙されたものを整形する、という用途に関しては強い。

たとえば、Rubyで%記法での文字列配列を作る場合などに使える。

f:id:mugi1:20181210092250g:plain
Rubyで%記法の文字列をサッと作る

いい感じに全部のケース変換をする

VSCodeはマルチカーソルとは別にケース変換ができる機能がある。 以下のプラグインを入れると、デフォルトに加えてさらに様々な変換も可能になる。

marketplace.visualstudio.com

そしてこれらはマルチカーソルと組み合わせ可能であり、それが非常に強力。

たとえば、APIレスポンスがキャメルケースだが、サーバサイドがRubyで書かれてるのでスネークケースにしたい場合とかがある。

f:id:mugi1:20181209180010g:plain
キャメルケースのレスポンス内容をスネークケースの変数に格納する

あるいは、Vueを使ってるとテンプレート上だけはケバブケースにしたい、みたいなことがある。

f:id:mugi1:20181209181422g:plain
Vueコンポーネントケバブケースでテンプレートに記述する

全行にデバッグログを突っ込む

愚直にデバッグログを仕込みまくる、みたいなことはコード書いてる人なら誰も経験があると思う。(ちなみにもちろんデバッガを使えるなら使ったほうがいい)

各行ごとの実行後の状態を見たい場合などは、出力するログに番号も採番しないと後で判別がつかないが、これがまた面倒くさい。

VSCodeには vscode-input-sequence という素晴らしい拡張機能がある。 marketplace.visualstudio.com

これとマルチカーソルを組み合わせることで一気に連番を振ったログ出力を差し込むことが出来る。

f:id:mugi1:20181209204227g:plain
連番を振ったconsole.logを一気に埋め込む

まとめ

というわけで、事例を元にしたマルチカーソルの使い方を紹介した。

特にここ最近はVSCodeを利用する人が増えてきたと思うけど、マルチカーソルを使わないとかなり勿体無いと思っているので、ぜひ積極的に試してみてほしい。

問題があるとすると、これに慣れるとシェル芸をしなくても大体のことが出来るようになっていくので脳が死んでいく。そして中毒性が高い。

VSCodeで開いてマルチカーソル使ったほうが速いことが多いけど、たまには意識的にターミナルでawkとかsedとか使って操作するようにしたほうがいいとは思う。


冒頭にも書いてありますが、解説編でキー操作を紹介しています。よければあわせてどうぞ。

mugi1.hateblo.jp


Misoca+弥生+ALTOA Advent Calendar 2018、次のエントリは @shinya_kubotaさんによるXamarinの話です!

Nuxt.jsでTypeScriptを使うために色々試して諦めた

最近色々あってあんまりコードが書けてなかったけど、これじゃあかんと思って再開した。

とりあえず、アツいと思ってるものをやろうかな〜ということで、VueFesで話を聞いてからNuxt熱が再燃してきたので、ibuosをNuxtで書き換えてみよう、というのを試していた。

React&MobX&TypeScriptからの置き換え

もともとがReact&MobX&TypeScriptで動いている。これを書き換えたい。

Nuxtを使う以上、

  • React -> Vue
  • MobX -> Vuex

は確定として、TypeScriptをどうするか?という問題が残った。

正直、TypeScript&VSCodeでコードを書く体験を一度味わうと、もうただのPureJSには戻れない感がある。

ということで、NuxtでTypeScriptを使うために色々頑張ってみた。

最終的にはタイトルにある通り諦めたのだが、これを再度構築するの結構ツラいし時間がかかると予想されたので、いつか役に立つかもしれないのでここに残しておく。

型定義を書く

あたりは自分で型定義ファイルを用意した。 @fukuiretuさんの記事やGistをだいぶ参考にさせてもらった。感謝。

最終的にはこんなのが仕上がった。

import Vue from 'vue';
import { Route } from 'vue-router';
import { Store } from 'vuex';
import { AxiosInstance, AxiosRequestConfig, AxiosPromise } from 'axios';

interface NuxtAxiosInstance extends AxiosInstance {
  $request<T = any>(config: AxiosRequestConfig): AxiosPromise<T>;
  $get<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>;
  $delete<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>;
  $head<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>;
  $options<T = any>(url: string, config?: AxiosRequestConfig): AxiosPromise<T>;
  $post<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise<T>;
  $put<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise<T>;
  $patch<T = any>(
    url: string,
    data?: any,
    config?: AxiosRequestConfig
  ): AxiosPromise<T>;
}

interface NuxtContext {
  isClient: boolean;
  isServer: boolean;
  isStatic: boolean;
  isDev: boolean;
  isHMR: boolean;
  route: Route;
  store: Store<any>;
  env: object;
  query: object;
  nuxtState: object;
  req: Request;
  res: Response;
  params: { [key: string]: any };
  redirect: (path: string) => void;
  error: (params: { statusCode?: String; message?: String }) => void;
  beforeNuxtRender: (param: { Conmponents: any; nuxtState: any }) => void;
  $axios: NuxtAxiosInstance;
}

declare module 'vue/types/options' {
  interface ComponentOptions<V extends Vue> {
    layout?: string;
    middleware?: string | String[];
    fetch?: (context: NuxtContext) => void;
    asyncData?: (context: NuxtContext) => void;
    scrollToTop?: boolean;
    transition?: string | object | Function;
    validate?: (context: NuxtContext) => boolean;
    head?: () => { [key: string]: any };
    watchQuery?: string[];
  }
}
declare module '*.vue' {
  import Vue, { ComponentOptions } from 'vue';
  const value: ComponentOptions<Vue>;
  export default value;
}

Vuexで型定義

TypeScript力が足りてないので、正直これであってるのかわからないが、こんな感じになった。 (コードは雰囲気で抜粋してる)

State

export interface State {
  user: User;
}

export const state = (): State => ({
  user: {
    id: null,
    displayName: '',
  }
});

Getter

import { GetterTree, ActionTree, MutationTree } from 'vuex';

export const getters: GetterTree<State, any> = {
  isSignin: (state: State) => state.user.id
};

Action

import { GetterTree, ActionTree, MutationTree } from 'vuex';

export const types = {
  SET_DISPLAY_NAME: 'SET_DISPLAY_NAME',
};

export const actions: ActionTree<State, any> = {
  async updateDisplayName(context, name: string): Promise<void> {
    await (this.$axios as NuxtAxiosInstance).$patch('/myself/name', { name });
    context.commit(types.SET_DISPLAY_NAME, name);
  },
}

Mutation

import { GetterTree, ActionTree, MutationTree } from 'vuex';

export const types = {
  SET_DISPLAY_NAME: 'SET_DISPLAY_NAME',
};

export const mutations: MutationTree<State> = {
  [types.SET_DISPLAY_NAME](state, displayName: string) {
    state.user.displayName = displayName;
  }
}

だいぶ意味不明な感じにはなってる。

  • vuexの XXXTree って使って大丈夫なのか?
  • this.$axios とかを型付けする方法がさっぱりわからない

といった悩みを抱えていたが、とりあえずある程度それっぽくなったのでこれで妥協した。

Vueファイル

たとえば、とある pages/ 配下のファイルからscript部を一部抜粋すると

import Vue from 'vue';
import Component from 'vue-class-component';
import { namespace } from 'vuex-class';
import { State as Auth } from '../store/auth';
import * as auth from '~/store/auth';
import { NuxtContext } from 'types';

const authModule = namespace(auth.name);

@Component({
  head: () => ({ title: 'マイページ' }),
})
export default class MyPage extends Vue {
  @authModule.State user!: auth.User;
  @authModule.Action updateDisplayName: any;

  name = '';

  asyncData(context: NuxtContext) {
    const user: auth.User = context.store.state.auth.user;
    if (!user.id) {
      return context.redirect(
        `/signin?r=${encodeURIComponent(context.route.path)}`
      );
    }
    return { name: user.displayName };
  }

  async handleUpdate() {
    await this.updateDisplayName(this.name);
  }
}

こんなことになった。

Propsなどを受け取るコンポーネント等の場合は、こんな感じ。

import Vue from 'vue';
import Component from 'vue-class-component';
import { Prop } from 'vue-property-decorator';
import { ellipsis } from '~/lib/text';

@Component
export default class EllipsisText extends Vue {
  @Prop({ default: '' })
  text!: string;
  @Prop({ default: 100 })
  size!: number;

  get ellipsisText() {
    return ellipsis(this.text, this.size);
  }
}

いずれにしても、

  • vue-class-component
  • vuex-class
  • vue-property-decorator

あたりを駆使して、うまく型情報を渡していくような書き方になる。

ずいぶん普通にVueを使う場合とは書き方が違うが、 @Props とかは解りやすくて良いな〜と思いながら書いてた。

ESLint & Prettier

正直色々いじりすぎて何のプラグインを入れたか記憶が薄いが、create-nuxt-appを叩いたときに作られる .eslintrc.js をもとに色々いじって、最終的にこうなった。

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  parserOptions: {
    parser: 'typescript-eslint-parser',
  },
  extends: [
    // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention
    // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules.
    'typescript',
    'plugin:vue/essential',
    'plugin:prettier/recommended'
  ],
  // required to lint *.vue files
  plugins: [
    'vue',
    'prettier'
  ],
  // add your custom rules here
  rules: {
    'typescript/no-var-requires': false,
    'typescript/no-non-null-assertion': false,
    'typescript/explicit-function-return-type': false,
    'typescript/no-angle-bracket-type-assertion': false
  }
}

確かこのあたりを使うのがポイントだったと思う。

  • eslint-plugin-typescript
  • eslint-config-prettier
  • eslint-plugin-prettier
  • typescript-eslint-parser

ビルド時に error TypeError: Cannot set property 'ts' of undefined が出る対策

こちらのissueを参考にして解決。 Nuxt Edge - Issue when running/building the project · Issue #48 · nuxt-community/typescript-template · GitHub

webpackにts-loaderを通してあげる。

front/modules/typescript.js

module.exports = function() {
  // Add .ts & .tsx extension to Nuxt
  this.nuxt.options.extensions.push('ts', 'tsx');

  // Extend webpack build
  this.extendBuild(config => {
    // Add TypeScript
    config.module.rules.push({
      test: /\.tsx?$/,
      loader: 'ts-loader',
      options: { appendTsSuffixTo: [/\.vue$/] },
    });

    // Add .ts extension in webpack resolve
    if (!config.resolve.extensions.includes('.ts')) {
      config.resolve.extensions.push('.ts');
    }

    // Add .tsx extension in webpack resolve
    if (!config.resolve.extensions.includes('.tsx')) {
      config.resolve.extensions.push('.tsx');
    }
  });
};

front/nuxt.config.js

module.exports = {
  ...
  modules: ['~/modules/typescript.js'],
  ...
}

結果

Nuxt.jsで、それなりに型で守られつつ、ESLint/Prettierでコードもきれいに書いていくことが出来るようになった。

正直、いろんなところで躓いて、ここに辿り着くまでに5〜6時間ハマり続けていた。

その後コードを書き進めて...

ここまで頑張ったのに結局かよって感じですが、最終的には、TypeScriptを併用するのはあきらめることにした。

大きい理由は2点ある。

  1. 記述が増えすぎてキツかった

    特にこれはVuex周りで思った。Vuexはサクサク書けるな〜という印象を持ってたんだけど、型で守ろうとすると記述がすさまじく増える感じがあった。

  2. 型付けのための記述のクセが強い

    型を付けようとした際に、クラスコンポーネントやデコレータといったものを駆使して、いかにTypeScriptに型情報を教えてあげるか、といったところがポイントになってくる。これ自体は凄くて、書いてみると「おぉ... Vueに秩序が..!!!」という感動も覚えた。
    ただ、ECMAScriptを使うような、いわゆる良く書くいつものSFCとだいぶ記述の差異が大きい点は考えないといけなくて、しばらく時間をあけてからコードを見たときに、「俺の知ってるVueと違う!」ってなってしまった。(私自身の記憶力とかの問題も大きい。)
    便利ではあるんだけど、標準のレールから結構外れるような感覚を覚えて、これをずっと面倒見ていける自信が沸かなくなってしまった。

とはいえ、これはあくまでも個人の、しかもほぼ誰もアクセスしてこないような小さい趣味サイトの開発するときの一人の感想でしかない。

ある程度の規模であったり、サービス・プロダクトの性質によっては、恩恵がコストを上回ることも十分考えられそうなので、お仕事で使う場合には様々な事情を考慮した上で吟味しないといけないな、と思った。(これはNuxtやTypeScriptに限った話ではないので、常日頃から意識していきたい。)

というわけで、燃え尽きて終了です。

「使っているもの」を管理・共有するWEBサービスを作った

f:id:mugi1:20180830225636p:plain

前々からコツコツ作っていて、形になったので公開した。

ibuos.net

「使っているもの」の管理・共有を主軸においたサービスです。 一人でやってるので荒削りな部分もあるんだけど、コンテンツが揃ってこそ楽しくなってくるので、抵抗がなければ触ってみてもらえると嬉しい。

ちなみに ibuos = 装備(soubi) を逆にしたものです。ダサいね!

できること

  • ログイン(Google or GitHub)
  • 使ってるものを登録 / 一覧
  • ユーザの使ってるものを見る
  • フォローできる(フォロー一覧がまだないので、フォローする意味はまだない!!)
  • 自分の表示名の変更

とりあえず、現状ではまだ機能は少なめです。

作ろうと思った動機

もともと、人の使っているガジェット・ツール・サービスとかに興味があって、社内のesaとかにもそういうまとめがあると積極的に見に行くし、自分も書き込んでいた。

社内だけじゃなくていろんな人のをみたいな〜、と思ったときに、一箇所にまとまってくれてたらいいのに、と思ったのが発端。

あと、単純に物を買うときに、Amazonのレビューとかも見るけど、それ以上に、「あの人が使ってるなら自分も使ってみよう!」っていう動機が多かった。

そういう人は自分以外にも多いんじゃないかと思っていて、そうであれば、眺めてるだけでも楽しめるんじゃないかな〜と思ってる。

技術的な話

フロントエンド

  • React
  • MobX
  • ReactRouter
  • TypeScript

バックエンド

  • Rails(API)
  • firebase
  • Heroku
  • CloudFlare
  • S3

あたりを使ってる

時代はNuxt.jsなのは把握しているけど、TypeScript&MobXを使いたいという欲求だけで突き進んでたら、気がついたらリリースしていた。

最初はSSRを頑張ろうと思ってたけど、まあ個人サービスだし別にええやろ!って思って、途中であきらめた。

もしかしたらいずれNext(or Nuxt)あたりで書き直すかもしれない。

仕事以外で、全てゼロから自分で準備して公開するっていう経験が実は無かっのので、色々と勉強になる部分もあるし、楽しい。 (とはいえ、Herokuとか使ってるのでだいぶチートっぽい感じはある)

なお、PostgreSQLも無料プランなので、ほうっておくとレコード件数上限で死ぬ未来が待っている。幸いにも使ってくれるユーザがいるようであれば、課金したいと思う。

今後の展望

とりあえず、直近では以下を作りたいと思ってる。

  • フォロー一覧・フォロワー一覧
  • ジャンルごとの一覧
  • タグ付け
  • 検索機能

というわけで、ぜひ使ってみてもらえると嬉しい。

そして、

  • エラー出たぞ!
  • こういう機能がほしいぞ!

みたいなのがあれば、@mugi_unoまで教えてもらえると嬉しいです。

React+MobX+TypeScriptが個人的にちょうど良い

Vue使いやすい

以前はフロントエンドといえばReact+Reduxでしょ!というのが主流だったが、最近ではVue+Vuex、あるいはNuxt.jsが注目を集めてる。

私自身も、趣味・業務両方でVueを使っていて、とても使いやすいな〜と思ってる。

TypeScript使うとちょっとつらい

TypeScript自体はかなり強力で、例えばkeyofのような機能はとても魅力的だったりする。

ただ、Vueと組み合わせてTypeScriptを使った型付けをしたい場合に、2018年7月現在ではわりと厳しい感じがしている。私が調べた限りでは、Vue&VSCodeを使うことでthisからの補完などはある程度はできるが、Vuexを絡めた場合には頑張って型を書く必要がある。(Nuxtの場合はもっと大変)

このあたりは、ClassComponentを駆使する有志作成のライブラリを使うことである程度はカバーできる。

ただ、人によって好みもあると思うけど、いわゆる一般的に見られるVueの書き方とは大きく離れることになってしまうことが多く、軽い気持ちで導入するにはハードルが高いな〜、と感じてしまう。

Reactの場合

Reactの場合はTypeScriptとの相性が良く、VSCodeを組み合わせた場合もかなり使いやすい。例えば、JSXに記述するpropsも厳密にチェックすることができる。PropTypesを使わずとも事前に検証できるのも心強い。

状態管理は?

王道だとReduxを採用することになると思うが、私はつらい。(これには個人的なトラウマによる偏見もかなり含まれている。)

これはもちろん開発対象のプロダクトやチームとの相性に依る部分もあるけど、Reduxはかなり記述が増えて、重厚なイメージがある。

もっと軽い感じで使いたい。

このあたりはVuexはとても良くできていて、理解もしやすい。

MobX

mobx.js.org

というわけで、ここ最近MobXを使ってみているが、これはいいな〜という手応えを感じている。

クラスベースでの定義

たとえば、Reduxで一通りのフローを作る場合

  • ActionCreator
  • Reducer
  • Store

あたりを書くことになるが、ファイルがめっちゃ増えたり、Reducerがなんかとんでもない感じに成長してしまうみたいなことがあった。Reducerがとにかくやばい。

MobXの場合は、状態管理をしたいクラスからインスタンスを作成して利用する。こんな感じ。

import axios from 'axios';
import { action, observable, toJS } from 'mobx';

export class Item {
  @observable public name: string = '';
  @observable public image: string = '';

  @action.bound
  public async save(): Promise<void> {
    const res = await axios.post('/items', toJS(this));
    return res.data.id;
  }
}

これが非常にわかりやすく、

  • 状態は @observable 定義のインスタンス変数
  • 処理や状態変更は @action.bound のメソッドを実行

だけで、非常にシンプル。実質ただのインスタンス

そしてTypeScriptと組み合わせた場合に嬉しい効果として、クラス定義されているので、別途型定義を書く必要がない。

Reactと組み合わせる場合にはこれが非常に嬉しく、Propsにクラス定義を指定しておくだけで、そこに紐づくプロパティが全部型チェックできる。

これが出来るかどうかということより、「意識せずに簡単にできる」というのが重要で、書いてて幸せな気持ちになれた。

Reactと組み合わせる

Reactと組み合わせる場合は mobx-react を使う。
このあたりの初期化周りは react-redux を使った場合と似てる。

ただ、ストアにするインスタンスを作って突っ込んでるんだな〜っていう感じで、わりとわかりやすい。

import { Provider } from 'mobx-react';
import { Item } from './Item';

const store = {
  item: new Item()
};

const App = () => (
  <Provider {...store}>
    ...
  </Provider>
);

export default App;

参照する場合には@inject を使う。 react-reduxのときの connectに近い。

import { inject, observer } from 'mobx-react';
import * as React from 'react';
import { Item } from './Item';

interface Props {
  item: Item;
}

@inject('item') @observer
export default class ItemContainer extends React.Component<Props> {
  public render() {
    return (
      <React.Fragment>
        <div>{this.props.item.name}</div>
        <ImageComponent item={this.props.item} />
        <button onClick={() => this.props.item.save()}>save</button>
      </React.Fragment>
    );
  }
}

Actionの呼び出しは、直接インスタンス変数のメソッドを呼び出してる。

この部分。

<button onClick={() => this.props.item.save()}>save</button>

これについては賛否両論あるかもしれない。

Reduxを使っていた場合に、子コンポーネントのReduxへの直接依存を避けるためにメソッドもPropsリレーをしてコードを書いたことがあるが、往々にしてバケツリレー地獄になって、最後には辛いなという気持ちになってた。

結局、「ActionをDispatchするのだけは子コンポーネントからでもOK」みたいになることも多くて、それと同じことをやってる感じ。

ただ、TypeScriptを組み合わせることで、どのメソッドがどのコンポーネントで呼ばれているのかを静的チェックすることができるので、これが非常に強力で、Actionがどこで実行されてるのか把握でき、リファクタリングをする際もある程度ビビらずに直していける。

TypeScriptと使う

というわけで、多々TypeScriptと組み合わせた場合のことを書いた通り、TypeScriptとの相性がかなり良いっぽい。

React+MobXだけで書いた場合には、「簡単に状態管理が出来てうれしい!」くらいで終わりそうだが、TypeScriptを組み合わせることで、クラスベースであることが一気に強力な機能に変わるような印象を受けた。

まとめ

というわけで、React+MobX+TypeScriptがかなり良さそうですよっていうのをどこかに書きたくて書いてみた。

やりたいことを「意識せずに」「簡単に」できるのが大事で、そのあたりを今の所ちょうど良い具合で実現してくれるのがこの組み合わせだな〜と感じてる。

良いことばかり書いてるが、趣味で書いている程度なので、大規模プロダクトの場合や、パフォーマンスが要求される場合などはどうなの?とか色々考えれてない部分はある。

けど、書いてて楽しいので、細かいことはいいやってなってる。

同じことを感じている人がいることに期待している。

とはいえ、Vueはやっぱり使いやすい。pugとかに慣れてると、JSXでclassNameって書くたびにアアアアアア!!!!ってなるし、気をつけててもネストが深くなりがちなのはつらい。

そもそもこれを試したのは「Vue+Vuex (or Nuxt) でTypeScriptを使いたいと思ったけど辛かったから」というのが大きいので、逆に言えばそちらが快適に書けるようになれば、そちらを使うことが多くなりそうではある。

ともあれ、何が一番良いかとかではなく、都度状況を見ながら最適なものを選べるようになっていきたいと思った。

終わり。