Vue.jsでゆるくAtomic Designをやる
これは Aizu Advent Calendar 2018 の 22 日目の記事です。
20 日目は @4rotsugd さんでC++ゲーム開発録 - 1週間で音ゲー制作、23 日目は @yt8492 さんのRaspberry Pi 3でAndroid Things入門です。
adventar.org
アドベントカレンダーは初めてでプログラミング初心者なのでお手柔らかにお願いします 🙇🙇
🙇
遅れてしまってすみません!!なんでもはしないけど許して下さい。
はじめに
本記事では Vue.js を使って Atomic Design でゆるく UI を作っていきます。Vue.js についての詳細やロジカルな部分にはあまり触れないのでご了承ください。ざっくりやっていきます。結構雑な理解です。
Atomic Design とは
ざっくり説明すると Brad Fros 氏によって提唱されている UI の設計手法です。UI を
- Atoms
- Molecules
- Organisms
- Templates
- Pages
の5段階で構成します。上から下に行くに従って抽象的なインターフェースから具象的なインターフェースとなります。
以下で簡単にそれぞれどのようなものか説明します。
Atoms
全ての UI を構成する基礎で、例としてはフォームのボタンや入力欄などが挙げられ、UI として機能が壊れない最小の要素です。
Molecules
Atoms の組み合わせで構成されます。比較的単純で再利用可能なように作成します。単純な機能を持つことが可能です。
こちらでは検索欄が例に挙げられています。後述の Organisms に分類したくなるかもしれません。ただ、検索欄自体は複数箇所で使用が想定でき、配置することによって意味が明確するはずです。例えば、ヘッダーに配置すればページもしくはその Web サイト全体の検索だと直感的にわかりますし、ユーザー一覧の上部に配置されていればユーザーを検索するんだなということがわかるかと思います。(プロダクトによって変えるべきだとは思います。)
Organisms
Atoms と Molecules の組み合わせ構成され、コンテンツとして完結しているような比較的複雑な UI の構成要素となります。例としては ヘッダーやフッターなどが挙げられます。
Templates
Atoms、Molecules、Organisms を組み合わせてページの雛形とするのが Templates です。実際に画像や文章などは流し込ません。
Pages
Templates に実際にコンテンツを流し込んがものが Pages です。文章や画像など実際に表示することができます。イメージとしては Templates というクラスのインスタンスを生成してできたものが Page でしょうか。(ホンマか?)
Vue.js で Atomic Design を実践してみる
ここまでで Atomic Design についてざっくり(本当にざっくりと雑に)と説明が終わりました。ここで抑えていて欲しいのは Atomic Design はデザインのための手法であってコンポーネントを作成するための手法ではないということです。ですから実際にコーディングしていく上では具象的な層から抽象的な層のデザインを決定してみたり、状態をどこに持たせるか?など考えることは多いです。
今回は、できる限り CSS は各 Atoms で適用して、状態はコンテンツとして完成し始める Organisms から持たせるという方針でやりたいと思います。ここは個人やプロダクトに寄る部分になるかなと思います。
実際のソースコードは以下に置いておきます。(名前か被るコンポーネントは My をつけてますが実際のプロダクトではやらないでくださいね!)
github.com
セットアップなど
まずはプロジェクトを作成するところから始めましょう。vue cli
を使用してプロジェクトの雛形を作成します。
$ vue create project-name
でプロジェクトが作成できます。実行するといくつか設定をどうするか聞かれますがここは個人で好きなのを選択していいと思います。私はPrettier が入る構成にするのをおすすめします。(オートフォーマットが非常に便利なため)
$ vue add storybook
でStorybookを導入して置くとコンポーネントカタログを作成でき便利です。が今回は Storybook でのコンポーネントカタログの作成方法は述べません。基本的な初期設定はvue cli
がやってくれます。
そして/src/components
以下にAtoms
、Molecules
、Organisms
、Pages
を作成しておきましょう。それぞれのディレクトリー以下でコンポーネントを定義していくことになります。
なぜTemplate
を作らないかといえばデータの流し込みは Vue の機能(もし他のフラームワークを使用するならその機能)を使用してデータを流し込むためです。逆にTemplate
を利用すると冗長な形になります。
ポイント
2 点ほど実装に入る前に Tips があります。まず 1 つ目ですが Vue ではすでに HTML 要素で使用されている名前はコンポーネントの名前として使用できないことです。
2 つ目はv-model
を独自定義したコンポーネントで使用するにはvalue
属性をvalue
プロパティにバインドし、input
イベントにて新しい値で独自のinput
イベントを発行する必要があるということです(v-model
は@input
と:value
の糖衣構文)。具体的には以下のように定義します。(一例です。他にも記述方法はあります)
<template> <input :value="value" @input="onInput"></input> </template> <script> export default { name: 'my-input' props: ["value"], methods: { // 親からさらに親へ伝えるときはeventではなくnewValueを渡すと良い onInput(event) { this.$emit('input', event.target.value) } } } </script> // 上記を提起することで以下のように使用可能 <template> <my-input @v-model="hoge"></my-input> </template> <script> import MyInput from "path/MyInput.vue" export default { name:"fuga", components: { MyInput } } </script>
それでは実際に各 1 つずつコンポーネントの実装を追っていきましょう。
Atoms
input
は上記の例にあるので button
を使ってみたいと思います。以下のような形になると思います。
<template> <button class="button" @click="onClick"><slot /></button> </template> <script> export default { name: "my-button", methods: { onClick() { this.$emit("click"); } } }; </script> // スタイルは省略
クリックイベントの発火を親に伝えて親でイベントをハンドリングするようにするだけですね。簡単!<slot></slot>
を使うことにより<my-buton>hoge</my-buton>
のような形でボタンに表示するテキストを制御できます。
また、なぜthis.$emit("click")
しているかといえば親でイベントを制御しないと URL からどの動作をするか判断したり、イベントの数だけボタンを定義することになるためです。また、ボタンの色やサイズなどこの中で決定してもいいですし、共通のスタイルだけ当てて色やサイズは親コンポーネントで決定するとより柔軟に扱えると思います。
Molecules
リンクをリストにしたものを扱ってみましょう。
<template> <ul> <li v-for="(link, key) in linkList" :key="key"> <link-list-item :to="link">{{ key }}</link-list-item> </li> </ul> </template> <script> import LinkListItem from "@/components/Atoms/LinkListItem.vue"; export default { name: "link-list", components: { LinkListItem }, props: { linkList: { type: Object, required: true } } }; </script>
Atoms にrouter-link
をラップしたLinkList
コンポーネントがあることとします。単純に親から遷移先の名前と URL のペアの Array を受け取ってそれをv-for
を使用し、リストにしています。
Organisms
Molecules でみたLinkList
を使用してみましょう。
<template> <nav> <my-title>Menu</my-title> <link-list :link-list="linkList" /> </nav> </template> <script> import MyTitle from "@/components/Atoms/MyTitle.vue"; import LinkList from "@/components/Molecules/LinkList.vue"; export default { name: "side-nav", components: { MyTitle, LinkList }, data() { return { linkList: { home: "/", page1: "page1", page2: "page2", page3: "page3" } }; } }; </script>
こんな形でサイドメニューを定義してみました。MyTitle
コンポーネントは見出しとして使用する想定です。ここでようやく状態を持つことを考えます。このコンポーネントでは遷移先に関する情報をもたせています。API からのレスポンスでリンクを生成したりする際に、より上位のコンポーネントで取得しこのコンポーネントに渡してもいいですが、これはここだけで完結可能と考えることができるのでもしリンクを API から取得するのであればこのコンポーネントでの定義も問題ないです。場合によって使い分けましょう。
Pages
今までのものを組み合わせ、API 通信などを行ったりして各ページを作成するだけですので割愛させていただきます。ポイントとしては共通部分はくくりだしてベースとなっているファイル(例: App.vue
)に置いてみたり、必要なレイアウトが複雑であればレイアウトを Atoms として切り出してみたりすると良いかもしれません。
まとめ
簡単にですが Atomic Design についての説明と Vue.js による実装をしてきましたが、いかがでしたでしょうか?コンポーネントの分類が難しかったり、私自身の理解が甘かったりしますが一通りの概念を把握してもらえれば幸いです。実際のソースではこの記事内で紹介していないコンポーネントも実装してありますのでクソコードだと思っていただいても OK ですし参考になればとても喜びます。
余談
冒頭に書いてあるプログラミング初心者は大嘘です(多分)
おわり
参考
HerokuにGo 1.11をデプロイして動かすときに躓いたのでメモ
HerokuにGo v1.11.1をデプロイするときに躓いたのでメモ
何が起きたか
go mod
を利用しライブラリを導入しHerokuにデプロイ使用とした。 go.mod
、go.sum
がディレクトリに存在し、ビルドパックにheroku/go
を指定しいざデプロイ!!したらアプリがクラッシュした。
解決策
vender
ディレクトリがプロジェクトルートに存在する必要があるみたい。なので go mod vender
で生成してやった。その後にデプロイ、無事にアプリが動作した。
ちなみにドキュメントを読むことで vender
が必要なことがわかったので今回はドキュメントをちゃんと読めた私は進歩している(正気か?
まとめ
HerokuでGoアプリケーション系の記事はバージョンが古いのばかりな気がするので公式のドキュメント & buildpackのドキュメントを読みましょう。デプロイ後にアプリが動かないことに気が付き、ドキュメントを読んで対処できました。めでたい🎉
jest-puppeteer.config.jsはプロジェクトルートになければならない
まとめ
ドキュメント読みましょう
configで設定したコマンドが動かなかった
e2eテストの環境環境構築をしていて以下のように書いていた
// jest-puppeteer.config.js module.exports = { server: { command: "PORT=3000 yarn dev", port: 3000 } }
こいつをtest/e2e/
に入れていたがコマンドが実行されることなく起動しなかった.
解決策としてはプロジェクトルートにjest-puppeteer.config.js
を入れればよかった...
ドキュメントにも書いてあるのでしっかりと読むべきだったが, jest-puppeteer
を使ってみた系の記事をいくつか見ていてどの記事もディレクトリに関する指定が書かれていない(本当か?)ため日本語の情報に飛びつく私はドキュメントを読むまでに時間がかかった...かなしい...
他にもsetupFiles
にファイルを指定しないで動かしたりしていたので気をつけたい.
Jestでcoverageがでなかったので自分の対処法メモ
現象
Jestを使用してテストを行っていたが、jest.conf.js
に以下のように書いていてうまくカバレッジが出なかった。
collectCoverageFrom: [ "src/**/*.{js, ts, tsx}", // その他設定 ]
これでカバレッジを見ると
----------|----------|----------|----------|----------|-------------------| File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | ----------|----------|----------|----------|----------|-------------------| All files | 0 | 0 | 0 | 0 | | ----------|----------|----------|----------|----------|-------------------|
こんな感じになってしまった。
解決策
上記のコードでは{js, ts, tsx}
のようにカンマの後にスペースが入っている。
これだとダメだったようで{js,ts,tsx}
のようにすると正常にカバレッジが表示された。
スペースが入ると動かなくなるものには気をつけていきたい💪
応用情報技術者試験に受かってしまった
TL; DR
点数が低いけれども受かってしまった...
何故受けたか
大学在学中に応用情報を取得しておきたかったのと普段絡んでる方々が取得済みで自分が持ってないのがなんだかなぁ...という感じ
合格まで
実は29年度の秋期を申し込んでいたが、週三十時間労働と夏バテで勉強ができず、当日も体調が悪かったため会場に向かえなかった(午前のボーダーを超えるくらいはできてたはず...)
改めて春期で申し込みを行い春休みを使いざっくりと勉強してちゃんと受験した。
午前対策は過去問道場のみで、午後は↓ を全部流し読みした。
2018 応用情報技術者 午後問題の重点対策 (午後問題対策シリーズ)
- 作者: 小口達夫
- 出版社/メーカー: アイテック
- 発売日: 2017/12/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
当日は午前問題は6割を超える確信を持てるくらいには解けていて(点数は低いが)午後が鬼門だった。
午後問題は
- 情報セキュリティ
- プログラミング
- システムアーキテクチャ
- データベース
- 組込みシステム
を選択した。
どの分野も午後問題の重点対策でそれなりにできていたはずなのだが、データベースが壊滅状態、組み込みは他の問題に時間をかけたため問題文をしっかり読めず適当な解答を生成してしまった。
その反面情報セキュリティはあっさり解けたし、システムアーキテクチャも同様に簡単な印象だった。
プログラミングは時間を半分以上費やしほぼ全部自身のある答えを書くことができた。(これが他の足を引っ張っている)
したがって、午後はセキュリティとアーキテクチャとプログラミングだけでほぼ点数をカバーしているのではないかと言うのが自分の持っている感想。
感想
カバーする範囲が広くしんどかった。特にネットワーク系は大学の講義の影響もあって全く触りたくなかったので午前問題でも勉強しなかったのは反省している。午後に関しても、プログラミングにあまりにも時間をかけすぎたのと、視野が狭くなっていて別の問題を選択してみるという行為が完全に頭から抜け落ちていたのは改善点だと思う。
上位の資格も挑戦していく気はあるのでインターンが始まる前に(行けるかわからないが)どれを受けるか決めて勉強を始めたいと思う。もっとちゃんと勉強します。
VSCodeがtsconfigで定義したpathsを使うと出たエラーの解決策メモ
importを楽にするためにtsconfig.json
に以下の設定を追記していた。
{ "paths": { "@/*": ["src/*"] }
しかし、VSCodeにてimport hoge from "@/fuga"
のように利用していたらモジュールが見つからないとエラーを吐いていた。
もちろん、コードはしっかりと動くので問題はなかったがエラーが出続けるのは精神衛生上悪いので解決策を探していた。
解決策
以下の拡張を導入することで解決した。
marketplace.visualstudio.com
結局よく原因がわかっていない🤔(vue-cliで生成したプロジェクトではエラーを吐かなかったため)
anyenvの導入でハマったのでメモ
anyenvの導入にハマってしまったので自分用にメモ.
anyenvとは
pyenvやrbenvなどの複数バージョンの管理を行えるツールを管理できるツールでコレがあることによって自分で設定ファイルに設定を追加せずに**envを利用刷ることができる.便利.
第一のハマりポイント - 既に .**env
が存在する
README通りに導入はできたが,どうにも**envが上手く動作しなかった.
ローカル,グローバルどちらに使いたいバージョンを設定してもうまく適用されなかった.
見つけたのがこちらの記事↓
どうも過去に**envを導入したことがあってhomeディレクトリ下に.**env
ディレクトリがあると上手く動作しないことが原因らしい.
第二のハマリポイント - 設定の記述するファイル
anyenvの導入には
export PATH="$HOME/.anyenv/bin:$PATH" eval "$(anyenv init -)"
を自分の設定ファイルに追加する必要がある.
自分は.zshenv
に上記設定を追加していて(なぜかは忘れた)上手く動作しなかった.
pyenvを使っていた時は上手く動作していたので中々気づくことができずにハマってしまった...
どうやら/etc/zprofile
内で /usr/libexec/path_helper -s
を実行してinitで追加されるパスを上書きしているのが悪いようだ.
.zshrc
にeval $(anyenv init -)
を追加することで解決した.設定ファイルの読み込み順の問題みたいなので/etc/zprofile
を読み込まなくするか,以降に読み込まれるファイルに設定を追加すると解決するみたい.
まとめ
2つの原因でハマっていてかなり困った.初期化処理が何をしているか理解すれハマらずにすんだと思うので,これからはその辺りも理解してやっていきたい💪