ReactとVueのどちらを採用するか

掲題について、とりとめもなくメリット・デメリットなどを考えているので、メモを残しておきます。

執筆時点ではReactは経験が非常に浅く、事実や経験というよりはほとんどが思い込みや予想です。

React

メリット

  • フロントエンドをうまく作るために頭が良い人たちが考えた設計や技法を効率良く学べそう
  • シェア一位のため事例が多く、かつFacebookTwitterといった大企業も採用しているため、大抵のことは解決方法が存在していそう
  • React NativeがあるのでNativeアプリ作りたくなったときにReactのノウハウを流用できるはず

デメリット

  • 位置づけがライブラリということもあり、react単体で実現できないことは別ライブラリと組み合わせて実現する必要があり、技術選定のための審美眼とどう組み合わせるかの設計スキルが必要(になりそう)
  • 正しい設計や技法を知らないうちはそれを学ぶまでの学習コストを払う必要があるため、とりあえず動かすまででも相当な時間がかかりそう

イメージ

  • フロントエンド専門あるいはフロントエンドからキャリアをスタートしているツヨツヨな人たちが好んで使ってる
  • 同じデザインかつコンポーネントの分割方針も同じだったらコードも似てくる気がしている
  • React書ける人ならVueとかAngularは楽にキャッチアップできそう
    • Reactを使いこなせる人はつよつよなフロントエンジニア

Vue

メリット

  • とりあえず一通り動かせるようになるまでの学習コストが低い
  • htmlベースベースでSFCを書けるため、HTML/CSS/JSの基本的な知識がある人には書きやすい
  • Vuex,Vue Routerといったコアライブラリを公式サポートしているので技術選定で迷いにくい
  • Nuxt.jsというたいていのアプリでは使われる(コア)ライブラリが全部入りになってる便利なフレームワークがあるので技術選定や設定に割く時間を節約できる
    • 本当にかなり使いやすい
    • RubyでいうRailsのような位置づけだと思ってる

デメリット

  • とりあえず動かせちゃう方法がたくさんあるせいで、正しい設計にたどり着くまでのコストが高い。またそれが故に設計スキルが身につくまでに時間がかかりそう。
  • TypeScriptとVuexの相性がイマイチ(らしい)
  • 事例は増えてきているが、まだシェアが低い
  • 個人(Evan You氏)がメインで進めているため、何らかの理由で彼が突然リタイアした場合に、その後のメンテが止まる可能性がある

イメージ

  • デザイナあるいはバックエンドといったような隣の領域からフロントエンドに手を伸ばしてきた人たちに好まれる
  • 同じ画面デザインかつコンポーネントの分割方針も同じでも、書く人によって全然違うコードになりそう
  • Vueの経験があるだけでは、ReactもAngularをすぐキャッチアップ出来るとは限らない
    • 意識的に設計スキルを磨いている人は別

どちらを採用するか

まず、大前提としてフロント開発自体の経験がない場合は、何を選んでも時間の試練に耐えうるものは作れないと思っています。

そのため、その状況だと何を選んだとしても割とすぐに捨てることになると思っています。

なので、敷居の低さを理由にまずはVueを採用するのがいいんじゃないかと考えています。

まずはVueで手早く価値を生み出せるものを作りつつ経験を積むべきな気がしています。

一通り作り終わればフロント開発の経験値がそれなりになっているはずなので、もう一度Vueで書き直した場合に今の課題を解決できるなら作り直すかどうかはおいといてVueを継続すればよいでしょう。

Reactでしか解決できないのであればReactで作り直すのが良いかと思います。

逆にフロント開発に強い人がチームにいるなら、好みで選んでしまって良いような気はしています。

価値を生み出すようになったアプリを、その後にフルスクラッチで書き直せる勇気や資金や時間が湧くかどうかはまた別のお話だと思ってます…

以上、私的メモでした。

レガシーをぶっつぶせ。現場でDDD!2nd 「インプット<アウトプット!」の参加レポート

レガシーをぶっつぶせ。現場でDDD!2nd 「インプット<アウトプット!」の一部二部に参加してきました。

過去に多くの勉強会に参加してきましたが、その中でも特に学びを得られた強い実感がありました。間違いなく上位に入る満足度です。

学んだことを反芻して、記憶やスキルとして定着化を図らねばもったいないと感じたのでまとめたものを記事に残しておきます。

全体的に期待していたこと

DDDは(軽量かもしれませんが)自分なりにいろいろと実践してきており、実践と内省のループから考え方がなんとなく固まってきたので新しいブレイクスルーを得るために他の人の考え方を取り入れたいと思って参加しました。

あるいは自分の考え方があっているかの答え合わせができればそれはそれで自信が持てるといいなとも期待して参加しました。1

システム設計の中でのドメインモデルの役割を体感する

このセッションの内容とされていたこと

公式のタイムテーブルより引用します。

このワークショップでは、ドメインモデルをどのように実装に落とすかではなく、 業務フローやユースケースドメインモデルがどのように関わり、 それらの変更がどのように伝搬するか、という全体の繋がりに焦点をあてます。

当日は、仮想的なシステム(の一部分)を題材として、 業務に変更が生じた際に何が変わるのかを、 モデルに対して付箋で変更を加えながら体験していただく予定です。

その場でワークショップを体験していただくだけでなく、持ち帰って、 自分たちのチームで実際の業務を対象にして試していただけるようにしたいと考えています。

この説明文を読んで私は『業務分析からそのモデリングまでの一連の流れや手法が知れること』と『それを現実に近い例で訓練できること』を期待して参加しました。

そして、その期待はドンピシャでした。

実際にやったこと

以下のようなことでした。

  1. カスタマージャーニーに似ているらしい手法(一般に通じる名前はつけられてなさそう)を用いながら、現状の業務をヒアリングしフロー図に落とし込む。
  2. そのフローで出てきた単語(概念)や操作(振る舞い)を概念モデルとして図に表していく。

これだけ書き出してみると単純かつ簡単な工程で大した意義がないように思えますが、フロー図作成で得られた気づき・見落としをモデルに反映する、あるいはその逆というフィードバックループが存在しており、概念モデルに現実の例を当てはめても通用するような表現力が育っていくような印象を持ちました。2

また、そんな印象を持てるほどの表現力を持ったモデルであれば、その後の実装はだいぶ楽になるだろうとも思いました。

感覚的な話ですが、『気づいていなかったことに気づく、それが可視化される。それが単純に楽しい/嬉しい』という成功体験が『他にも同じようなものを見つけてアハ体験したい』というモチベーションに繋がり、正のループが回っていたように思えます。

今まで業務でやってきたことを振り返る

私は今まで業務分析では開発チームでドメインエキスパートにヒアリングし、その中でそれぞれが構築したメンタルモデルを(何らかの方法論を用いて共有しあう事をすることなく)それぞれがすぐにコードに落とし込む、というようなことを繰り返してきました。

しかし、フロー図とモデル図に表すことでタンジブルにし、他の開発メンバーの意見を聞きながら足りていなかった「何か」を相互に教えあい、それぞれの図に反映していくことでコードを書くよりも低いコストで先に合意に達しておく手段があるとしることができました。しかもそれが現実的にも可能そうな感触を得られることができました。

これに気づけたのはとても有意義なことでした。

今までは、フロー図やモデル図を書けるほど具体的なイメージが持てているのであれば、動かせるコードを書いてそこからフィードバックをもらったほうが早いと言う考えでしたが、図を用いてチーム内で合意を形成していたほうが認識のすり合わせコストが減り時間的トータルコストとしては下がるのかもしれない、また他人の力を借りてより高速に正解に近いメンタルモデルを構築できるのではないかという考え方に変わりつつあります。3

概念モデル/ドメインモデルは実装に素直に落ちるらしい

ところで、『概念モデルとドメインモデルは実装に素直に落ちる』4という意味にとれる説明がセッションの冒頭にありました。

これは私が仮説として持っている考え方だったので聞いたときは『お、おおぉっ?おまおれ?やっぱそうなんや!』と嬉しくなった瞬間でした。

概念モデルをコードに写し取る例考えて、登壇者に質問しに行きましたが、『ありえる実装だと思いますよ!』と言って頂けて少し自信が持てました。

成果物

f:id:kogayushi:20191215100946j:plainf:id:kogayushi:20191215100741j:plain
一部成果物

得られたこと

  • 図の作成のための議論の中で自分が持つメンタルモデルと他の人が持つメンタルモデルは当然ながら違うという再認識ができた
    • そして、概念モデルの時点でもなかなか合意に至らないのに、それをやらずにコードを書いてしまえばそりゃみんなバラバラな書き方するよなと納得した
  • 概念モデルなどの図を作るコストを払ったほうがいきなりコードを書くコストを払うより低く済む可能性を感じられた
  • 概念モデルがうまく構築できていれば実装にも素直に落とし込めるという考え方を持っている人が他にもいることが知れた

その他の振り返り的なこと

あとから考えて面白かったのはかなりクラス図に近いものをドメインエキスパートに見せながら、多重度の話をしたり、関連の線をつなげたりして認識合わせをしていたことです。

今回私のチームの担当をして頂いた方はデザイナさんでしたが、デザイナである彼にこういう話し方が通じるのであれば完全なビジネスサイドの人にも教育すれば同じやり方が出来るのではないかという可能性を感じました。

一緒に仕事しているビジネス側担当者はPlantUMLを書いてみちゃうくらいこっちよりの感覚がある方なので、ぜひこれも試してみたいと思いました。

アクションプラン

フロー図とモデル図を試す

  1. (元になっているかもしれない)カスタマージャーニーを研究する
  2. このワークショップでやったことも踏まえて(1)を実践する(必要性を感じたら先にカスタマイズもする)。
  3. 現場で試す
  4. (3)で得られたフィードバックを元に、辞めるかカスタマイズ(を繰り返しながら)して運用を継続する。
  5. 繰り返し

概念モデルを使ってビジネスサイドと会話してみる

  • ビジネスとの認識合わせにモデル図っぽいものを使ってみる
    • そのために教育もちょっとだけ頑張ってみる

モデリングワークショップ 〜割り勘ドメイン編〜

このセッションの内容とされていたこと

これも公式のタイムテーブルより引用します。

"飲み会 会計時の割り勘をテーマにモデリングワークショップを行います。支払合計金額を人数で割る「割勘」は単純です。しかし上司なら比率多め、後から来た人や学生なら少額といった多少面倒な計算が伴います。また、幹事は負担ゼロか、同様に支払うのかのオプションもあります。

今回はこういった具体例でモデリング力を鍛えることが目的です。実際には、想定しているユースケースに対してドメインモデルを考え、すぐにコード上の型を作り(処理は後回し)チームメンバーと議論し、ワークショップを体験していただきます。

追記(11/21): 対象言語は時間的な都合で効率的にサポートできるJavaに限定させてください。Javaの言語的なサポートはスタッフが行います。不明な点はスタッフに確認してください。 また、当日はお題の公表と共に、モブプログラミング形式で5チームに分かれていただきます。その際にJavaが分かる方がドライバーとして必ずチームに含まれるように調整しますので安心してください。当日利用するテンプレートプロジェクトは運営側で用意したものを配布させていただきます。

追記(12/10): お題とひな型となるソースコードこちらです。詳しくはREADME.mdをご覧ください。当日は2時間しか時間ないのでできれば事前にドメインについて考えてきてください。 ※スタッフのほうでもPCは持ち込みますが、ワークショップ参加予定の方々はPCはご持参ください。"

この説明文を読んで私は、書かれていること通りに『モデリング力を鍛える』体験が得られることを期待して参加しました。

期待したそのものではありませんでしたが、『モデルと実装を行き来することでモデルもコードも育つ』ことが現実にあることだという実感を得ることができました。

この強烈な体験が余韻として残っています。

実際にやったこと

以下のようなことでした。

  1. 想定している割り勘の文脈を登壇者よりざっくり教えてもらう
  2. (1)をもとにチームで用語(概念)の洗い出しと相互の関連やそれぞれの属性と振る舞いを洗い出す
  3. (2)を見ながら実装する

ちなみに、(3)のフェーズではドライバーを務めました。

が、『お前ファシリテータか?』と思われるくらいコードを書きながらペラペラ喋ってしまいました。

役割に徹することができず、ちょっとでしゃばっちゃったなと少し反省しています…けど楽しかったですw

今まで業務でやってきたことを振り返る

一部で書いたことと重複しますが、図で示す前にコードを書いていました。

コードを書いている途中で当然新しい気付きはありましたが、それを図にフィードバックしていなかったため、新しい概念を獲得したことを強く実感することなく、それにより別の箇所を改善できる可能性にきづけることもなく5コードをひたすら書いていた気がします。

しかし、モデルで可視化&共有し合うことを知ったため、コードで得られた気づきをモデルに反映する、あるいはモデルで得られた気づきをコードに反映する、そういったそれぞれで得た気づきを反映し合うループを試しつつチームで共有する有意義さを体験できました。

成果物

ドメインモデル

f:id:kogayushi:20191215101128j:plain
二部成果物

ソースコード

github.com

得られたこと

  • モデル図とコードを行き来することでそれぞれの足りないところを補完できる、それを複数人で実施することで足りない観点を補え合える、という体験ができた
  • モデリング時には見落としていた概念が、実装の中で浮かび上がってくるという体験ができた
    • モデルとコードを行き来する価値や醍醐味を知ることができた

アクションプラン

  • 開発チームでモデル図を作成する
  • コードを書いてフィードバックが得られればそれをもう一度モデル図に反映する

まとめ

  • 今回の開催意図である『アウトプットをするほうが学びになる』の通り、ワークショップでアウトプットすることで学びとその強烈な体験を得られました。
  • 個人的には、他の人の考え方を取り入れたい、あるいは答え合わせをしたいという期待で参加しましたが、そのどちらも満たせました。
  • 学んだことを研究しつつもなるべく素直に現場で実践して行きます。

  1. 具体的なことは特に触れませんが、どちらも達成できました。

  2. 印象だけの話で現実ではこの短い時間だけでは足りず、どう少なく見積もっても2倍か3倍の時間は要するだろうと、振り返りながら思っています。

  3. そして二部でコードによるフィードバックの体験を得て、相互に行き来することが最強という結論に至ります。

  4. あとで質問しに伺ったところ、言い切ってしまうと少々語弊があり、実際はいろんな実装手段があるだろうと訂正されていました、私は実装を連想しやすいという意味で「素直に」という言葉の選択は間違ってないと思います。

  5. コードは詳細なので全体が俯瞰しにくくなかなか気づけない

Qiitaでも記事を書き始めました

ちゃんと検証した技術記事は今後Qiitaに投稿するようにしようと思います。

このブログはアイディアや、検証結果・内容に自身がない技術ネタなどを低頻度で更新していきます。

ちなみに、最近Qiitaに投稿したのはこの2つの記事です。

Spring MVCのRestControllerのRequestParamで任意のEnumをコードなどの別の値で受け取る方法 KotlinでDoma2のEnumドメインクラスを実現する方法

以上です。

ログ収集基盤の(再)設計

はじめに

個人的に考えていることのメモです。真似してやけどしても責任持てません。

データサイエンティストに聞いてわかったデータ分析基盤アーキテクチャの勘所ってのを以前書きましたが、その続きのようなポストです。

おことわり

現在関わっている案件の要件を前提とした設計です。常にこれが正しいとは思えないので、参考程度にどうぞ。

前提(非機能要件的なもの)

  • ログは可視化したいし、ビジネスサイドが気軽にそれを参照したい
    • Elasticsearchにつっこんで、kibanaで可視化しておく。
    • それをビジネスサイドに共有しておけば気軽に参照してもらえる(はず)
  • 生ログは全件残す
    • つまり、S3に未整形のまま全件をputしておく必要がある
  • Elasticsearch Serviceに高いお金は払いたくない(というか払えない)
    • Indexを細かく分けて不要なログは一定期間経過後にローテートすることで容量を落としてコストを削減する
      • syslogとappのindexはわけておいて、syslogは1週間程度残しておく(障害調査用)、appはずっと消さない、とかをやりたい。

思いついたもの

この非機能要件を満たすアーキテクチャで思いついたのは以下の2つです。

fluentd -> S3 + elasticsearch形式

アーキテクチャ

f:id:kogayushi:20191002113525p:plain
fluentd -> S3 + elasticsearch

ETL

  • fluentdでログの仕分けをしてindexにわける
    • pod-{namespace}-{application name}-YYYY.MM.DDあたりのindex名にすればいいんじゃなかろうか
      • istio関係のログはnamespaceで仕分けられるので、culatorでローテートできるようになるはず
      • kube-system関係のログも同上
  • syslogの取得を忘れずに
    • syslog-{tag}-YYYY.MM.DDあたりのindex名にすれば良いんじゃなかろうか
    • fluentd-elasticsearchのhelm chartのconfigmaps.yamlが参考になりそう
      • ただ、この例だとsyslog全部は取得しているわけではない(特に/var/log/dmesgがないのが気になる)っぽいけど、それで良いのかはわからない

メリット

  • シンプル
  • fluentdさえ理解していれば全体像が把握できる

デメリット

  • fluentdを理解して、設定ファイルを作り込む必要がある
  • ログデータの永続化先が増えたときにfluentdの設定ファイルを更新する必要があり、設定が肥大化&複雑化していきそう
  • バックプレッシャーが効く箇所がfluentdだけのため、ログ欠損の可能性が高いかもしれない。またスケールしないかもしれない。かもしれないだけで、規模が小さければ問題にならないかもしれない。

fluentd -> kinesis形式

アーキテクチャ

f:id:kogayushi:20191002113556p:plain
fluentd -> kinesis

ETL

  • やることはログの中身に応じてindex名を動的に決める
    • つまりfluentd -> S3 + elasticsearch形式と同じことをlambdaで必要がある

メリット

  • ログデータの永続化先が増えたとしても、lambdaを拡張していけばいい
    • lambdaから別のlambdaを読んだりできるので、好きなようにできる
  • kinesis streamsでバックプレッシャーが効くしスケールするサービスなのでアクセスがいくら増えても捌ける(はず)
    • 今の案件だと将来も含めて大量アクセスされることはまずないけど…

デメリット

  • 利用するサービスが増えるので全体像の理解に苦労しそう
  • lambdaの箇所でコケると詰まるかも?検証したほうが良い。
  • lambdaでindexを動的に指定したり、elasticsearchが解析できるように構造化してあげたりと、実装が大変そう

cluster-autoscalerでnodegroupを0にスケールする方法

ASGにk8s.io/cluster-autoscaler/node-template/label/{key}={value}のタグをつけるだけでした。

たったこれだけです。公式にも書いてあります

たどり着くのに数時間かかってしまったので他の日本人の助けになれば、と思って残します。。

Nuxt.jsのSPAにvuexでサイドメニューを実装する

はじめに

サイドメニューを実装する機会があり、手っ取り早い方法がvuexだったためvuexを使って実装してみました。

そのやり方を残しておきます。

どんなアプリか

実際の動きをみてもらったほうが早いです。

f:id:kogayushi:20190824233448g:plain

こんな感じでメニューをクリックしたら対応するページに遷移しつつ、そのページのメニューが選択状態になります。

今回もgithubにあげています。

使っているもの

実装時に意識して使っていたもの雑多に並べます

  • nuxt
  • vuetify
    • v-app-bar
    • v-menu
    • v-list
    • v-tabs/v-tab
  • vuex
  • nuxt-link

どんな設計になってるか

概要はこんな感じです。

  1. ページ遷移のタイミングでmiddlewareを使ってroute.nameをvuexに保存する
  2. 現在のページに対応したサブメニューの情報をvuexのgetterから取得する
  3. 取得したサブメニュー情報を元に動的に画面を表示する
  4. グローバルナビはホバーするとサブメニューのリストを出す。
  5. 現在表示されているページに対応するグローバルナビのメニューを選択状態にする
  6. 現在表示されているページに対応するサイドメニューを選択状態にする

(ざっくり)コード解説

設計とはきれいに対応していません。あしからず。

ページ遷移のタイミングでmiddlewareを使ってroute.nameをvuexに保存する

vuexに現在のページを保存するためのstateとmutationを作る

~/store/index.js
export const state = () => ({
  currentPage: 'index'
})

export const mutations = {
  setCurrentPage(state, payload) {
    state.currentPage = payload
  }
}

middlewareを使ってroute.nameを取得して保存する

~/middleware/pageId.js
export default function({ route, store }) {
  store.commit('setCurrentPage', route.name)
}
nuxt.config.js
  router: {
    middleware: 'pageId' // nuxtに教えておかないと動かない
  }

現在のページに対応したサブメニューの情報をvuexのgetterから取得する

各カテゴリのサブメニューの定数を定義する

~/store/index.js
const TOP_SUBMENU = {
  title: 'index',
  menus: [
    {
      pageId: 'awesome',
      name: 'awesome',
      url: '/awesome/menu1'
    },
    {
      pageId: 'wonderful',
      name: 'wonderful',
      url: '/wonderful/menu1'
    }
  ]
}

const AWESOME_SUBMENU = {
  title: 'awesome',
  pageGroup: 'awesome',
  menus: [
    {
      pageId: 'awesome-menu1',
      name: 'awesome-menu1',
      icon: 'phone',
      url: '/awesome/menu1'
    },
    {
      pageId: 'awesome-menu2',
      name: 'awesome-menu2',
      icon: 'email',
      url: '/awesome/menu2'
    },
    {
      pageId: 'awesome-menu3',
      name: 'awesome-menu3',
      url: '/awesome/menu3'
    }
  ]
}

const WONDERFUL_SUBMENU = {
  title: 'wonderful',
  pageGroup: 'wonderful',
  menus: [
    {
      pageId: 'wonderful-menu1',
      name: 'wonderful-menu1',
      icon: 'inbox',
      url: '/wonderful/menu1'
    },
    {
      pageId: 'wonderful-menu2',
      name: 'wonderful-menu2',
      icon: 'move_to_inbox',
      url: '/wonderful/menu2'
    },
    {
      pageId: 'wonderful-menu3',
      name: 'wonderful-menu3',
      icon: 'send',
      url: '/wonderful/menu3'
    }
  ]
}
stateにsubmenuを登録する
export const state = () => ({
  currentPage: 'index',
  submenu: {
    index: TOP_SUBMENU,
    awesome: AWESOME_SUBMENU,
    'awesome-menu1': AWESOME_SUBMENU,
    'awesome-menu2': AWESOME_SUBMENU,
    'awesome-menu3': AWESOME_SUBMENU,
    wonderful: WONDERFUL_SUBMENU,
    'wonderful-menu1': WONDERFUL_SUBMENU,
    'wonderful-menu2': WONDERFUL_SUBMENU,
    'wonderful-menu3': WONDERFUL_SUBMENU
  }
})
pageIdからサブメニューを取得するためのvuexのgetterを定義する
export const getters = {
  submenuOf: (state) => (pageId) => {
    if (state.submenu[pageId]) {
      return state.submenu[pageId]
    }
    return TOP_SUBMENU
  }
}

ちなみにこのgetterにはディスパッチテーブルと言われるデザインパターンを使っています。 これにより、今後カテゴリが増えたとしても、それに対応するサブメニューの定数を宣言し、それをstateに設定すれば対応完了できます。

取得したサブメニュー情報を元に動的に画面を表示する

現在のページを取得するためにvuexのgetterを定義する

export const getters = {
  currentPage(state) {
    return state.currentPage
  },
  submenuOf: (state) => (pageId) => {
    if (state.submenu[pageId]) {
      return state.submenu[pageId]
    }
    return TOP_SUBMENU
  }
}

サイドメニューコンポーネントを作る

<template>
  <v-card class="mx-auto" max-width="300" tile>
    <v-list>
      <v-subheader>{{ title }}</v-subheader>
      <v-list-item v-for="menu in menus" :key="menu.pageId" :to="menu.url" nuxt>
        <v-list-item-icon>
          <v-icon v-text="menu.icon"></v-icon>
        </v-list-item-icon>
        <v-list-item-content>
          <v-list-item-title v-text="menu.name"></v-list-item-title>
        </v-list-item-content>
      </v-list-item>
    </v-list>
  </v-card>
</template>

<script>
export default {
  computed: {
    title() {
      return this.$store.getters.submenuOf(this.$store.getters.currentPage)
        .title
    },
    menus() {
      return this.$store.getters.submenuOf(this.$store.getters.currentPage)
        .menus
    }
  }
}
</script>

vuexから値を取得し、その値に応じてメニューが書き換わるようになってます。

読めばわかると信じて解説はしません。雰囲気で感じ取ってください。

f:id:kogayushi:20190824233534j:plain

ヘッダー(グローバルメニュー)コンポーネントを作る

<template>
  <v-app-bar color="#6A76AB" dark :scroll-target="scrollTarget">
    <v-toolbar-title>
      Side Menu Example with Nuxt.js and Vuex
    </v-toolbar-title>
    <template v-slot:extension>
      <v-tabs align-with-title background-color="transparent">
        <v-tab
          v-for="menu in grobalMenus"
          :key="menu.pageId"
          :to="menu.url"
          :class="{ 'v-tab--active': isSamePageGroup(menu.pageId) }"
          nuxt
          v-on="on"
        >
          <v-menu open-on-hover offset-y>
            <template v-slot:activator="{ on }">
              <span text v-on="on">
                {{ menu.name }}
                <v-icon right>arrow_drop_down</v-icon>
              </span>
            </template>
            <v-list class="grey lighten-3">
              <v-list-item
                v-for="submenu in menus(menu.pageId)"
                :key="submenu.pageId"
                :to="submenu.url"
                nuxt
              >
                {{ submenu.name }}
              </v-list-item>
            </v-list>
          </v-menu>
        </v-tab>
      </v-tabs>
    </template>
  </v-app-bar>
</template>
<script>
export default {
  props: {
    scrollTarget: {
      type: String,
      required: true
    }
  },
  computed: {
    grobalMenus() {
      return this.$store.getters.submenuOf('index').menus
    },
    menus() {
      return (pageId) => {
        return this.$store.getters.submenuOf(pageId).menus
      }
    },
    isSamePageGroup() {
      return function(pageId) {
        return this.$store.getters.isSamePageGroup(pageId)
      }
    }
  }
}
</script>
  • ホバーするとサブメニューが表示されるようにしてあります。
  • サブメニューはvuexから取得しています。
  • 本当はcomputedで定義している内容はpropsで渡したほうが良いと思いますが、今回の趣旨ではないのでサボっています。

読めばわかると信じて詳しい解説はしません。雰囲気で(ry

作成したコンポーネントを利用するようにdefault.vueを修正する

<template>
  <v-app>
    <v-content>
      <v-card class="overflow-hidden" height="100%">
        <the-header scroll-target="#scrolling-techniques-4" />
        <v-sheet id="scrolling-techniques-4">
          <v-container grid-list-md text-xs-center>
            <v-layout row wrap>
              <v-flex xs3>
                <side-menu />
              </v-flex>
              <v-flex xs>
                <v-card tile height="100%">
                  <nuxt />
                </v-card>
              </v-flex>
            </v-layout>
          </v-container>
        </v-sheet>
      </v-card>
    </v-content>
    <v-footer fixed app>
      <span>&copy; 2019</span>
    </v-footer>
  </v-app>
</template>

<script>
import TheHeader from '~/components/TheHeader'
import SideMenu from '~/components/SideMenu'

export default {
  components: {
    TheHeader,
    SideMenu
  }
}
</script>

ヘッダーとサイドメニューを配置しているだけです。

まとめ

このような感じで、vuexを使って現在表示しているページに対応するサブメニューを表示してみました。

割と簡単に実装出来たし拡張もしていけるようにしたつもりですが、いまいちこれで本当に良いのか自信がありません。

もっと良い実装方法をご存知の人は是非教えて下さい。

参考にしたサイト

https://qiita.com/natsumi527/items/189d8e122bf2ee02f099

TypeScriptを学んでいる

TypeScriptを学び始めている中で感じていること

実践TypeScriptをざっくり読んで、ざっくりとTypeScriptについて理解したので現時点の感想を残しておく。

はじめに

自分用メモの位置づけの記事です。

現時点で好印象を抱いています。

感じた違和感

今まで身につけてきたオブジェクト指向を前提とした言語のパラダイムとはなんかちょっと違う感じがする。

特に、気に入らないわけじゃないが型定義ファイルに慣れない。違和感の正体はまだわからない。

理解したメリット

ピュアなJSだと、戻り値の型が不明で脳内デバッグしながら書くコストがあるのを少し問題に感じていた。

また、Vueだとコンポーネントにどんなオブジェクトを渡せば良いのかが対象のコンポーネントの中身を読まないとわからなかったりする問題があると感じていた。

型定義があるおかげでそれらが改善されるのは理解した。

null許容とnull非許容には好印象

null安全を実現できる言語仕様は良い。kotlinを学んだときも思ったが、コードの書き手にnullのときのハンドリングを強制できるのは良い。

絞り込まれていく型推論

型定義には違和感がありはするが、型チェックで型推論される型がどんどん絞り込まれていくのには書きやすそうな印章を受けた。この良さを実感すれば型定義を書くことの違和感は消えるのかもしれない

VS Codeが前提

ただし、型推論の恩恵を受けるためにはVS Codeを利用するのが大前提になる…のかな

まだ、Intellijでの書き心地を試してないがきっとそうだと思う

重厚感はある

確かに壊れたらある程度はコンパイルエラーで気づけるので大規模なアプリには適していそう。

同様の理由で長期間利用される想定のアプリにも適しているだろう。

例えば、propsの渡し方が変わったときとか。

寿命が短かったりずっと小規模のままが想定されるアプリにはちょっと重厚すぎる印象ではある。

弊社サービスには現時点では採用不可

覚えてもらうのにかかるコスト以上のメリットが返ってくるイメージがわかなかった。

教えるコストがそれほどかからないなら採用可能かもしれないが、今すぐ採用すると全員にペアプロで教えるレベルの手間がかかるはず…。

今後の計画

実践編のReact/NextとExpressを読み飛ばした&それ以外の箇所も写経していないので、まずVue/Nuxtのチャプターの写経をする。

その後、導入編を読み直しての気になった章と理解が浅い章の写経をする。

おそらく、1ヶ月強はかかるのではなかろうか。

それが済んだらフロントエンドはSPAをNuxt.jsとTypeScript(とVuetify)で、バックエンドはRESTful APIをKotlinとSpring Boot 2.1.xで、という構成で何かアプリでも書いてみよう。

ちょっと前に社内で少しだけ話題になってたThanks Cardのアプリのプロトタイプ1でも作ってみようかな?

最後に

まだまだ理解が浅いのでもっと学ぶと意見が変わると思う。

少なくともすぐに自社サービスに採用できないからといって、途中で学習を辞めようとは思わないくらいには可能性を感じている。

キャッチアップが終わったときに印象がどうかわっているか、楽しみ。


  1. これのパクリアプリ