- 投稿日:2021-03-21T23:27:14+09:00
tailwindcss 使用してわかったこと (tailwindcss + stylus)
結論
tailwindcss + stylusしんどい。
以下リストに当てはまる方にみていただきたいです。
- tailwindcssとsassやらstylusなにを組み合わせようか悩んでいる方
- tailwindcss + stylusを使っているけどなんか詰んだ方
使用環境
Vue CLI 4.5.11
tailwindcss 2.0.2なにがきつかったか
実際に使ってみて、きつかった点をアウトプットしてみました。
1.stylusじゃない(え?)
stylusの利点といえば、
:(コロン)
;(セミコロン)
{}
が不要なので記述が楽という点ですよね。example.css//cssパターン .example { width: 50%; height: 50%; }example.styl//stylusパターン .example width 50% height 50%上記コードは一緒 exampleクラスに対して
横幅50%
縦50%
を指定しています。tailwindcssをstylusと組み合わせて書くと以下のようになります。
Example.vue<style lang="stylus"> @css { .example { @apply w-1/2 h-1/2; } } </style>stylusを用いてtailwindcssの
utility class
をセットする際は@css
というものを定義して、その下にスタイルを設定していきます。
{}
がないとダメです。見た目は完全にstylusではないですね。2.
@css
を回避できない実は
@css
を使わず記述が通るパターンがあります。Example.vue<style lang="stylus"> .example @apply w-1/2 h-1/2; </style>お!いけるやん!なるのですが使えないパターンがあります
Example.vue<style lang="stylus"> .example @apply w-1/2 h-1/2 focus:w-1/4; </style>focusは使用できません。
Example.vue<style lang="stylus"> @css { .example { @apply w-1/2 h-1/2 focus:w-1/4; } } </style>@cssをつけないと
3.メディアクエリ
tailwindcssにおけるメディアクエリの指定の仕方は3パターンほどあると思います。
その中でstylusとうまくやっていける方法をみていきます。1.
@screen
- ダメなパターン
@screen
はネストさせることができない。。Example.vue<style lang="stylus"> @css{ .example { @apply w-1/2 h-1/2; @screen sm { @apply w-1; } } } </style>
@screen
を使うのであれば以下のようにするといけます。Example.vue<style lang="stylus"> @css { .example { @apply w-1/2 h-1/2; } @screen sm { .example { @apply w-1; } } } </style>2.
@media
@media
はネストの場合も使用することができます。
- ネスト
Example.vue<style lang="stylus"> @css { .example { @apply w-1/2 h-1/2; } @media (min-width: theme('screens.sm')) { .example { @apply w-1; } } } </style>
- ネストなし
Example.vue<style lang="stylus"> @css { .example { @apply w-1/2 h-1/2; } @media (min-width: theme('screens.sm')) { .example { @apply w-1; } } } </style>冗長感が否めないですね。
Breakpoint prefix
utility class
に対してBreakpoint prefixをしようすると指定したBreakpointでスタイルを適用させることができます。Example.vue<style lang="stylus"> @css { .example { @apply w-1/2 h-1/2 sm:w-1; } } </style>
sm:
というものがBreakpoint prefixにあたります。
いまは同列で扱っているのですが以下のようにもできます。Example.vue<style lang="stylus"> @css { .example { @apply w-1/2 h-1/2; @apply sm:w-1; } } </style>わざわざ二列にする必要なくね??ってなると思いますが、例えば以下のようなパターンだとメリットを感じれると思います。
Example.vue<style lang="stylus"> @css { .example { @apply pt-0 pr-0 pl-0 pb-0 rounded-full focus:outline-none focus:ring-2 focus:bg-green-primary sm:mr-0 sm:ml-auto sm:rounded sm:sticky sm:row-start-1 sm:col-start-4 sm:col-end-5 sm:max-w-13 sm:w-full; } } </style>上記コードだと長くて追うのがきついです。。
なので以下のように2行にすると多少はすっきりしますよねExample.vue<style lang="stylus"> @css { .example { @apply pt-0 pr-0 pl-0 pb-0 rounded-full focus:outline-none focus:ring-2 focus:bg-green-primary; @apply sm:mr-0 sm:ml-auto sm:rounded sm:sticky sm:row-start-1 sm:col-start-4 sm:col-end-5 sm:max-w-13 sm:w-full; } } </style>メディアクエリを使用するならBreakpoint prefixが一番良さそうな気がしてきます。
おわりに
以上がtailwindとstylusを組み合わせて使うのしんどい話でした。
まだ使ってみてそんなに時間が経っているわけではないのでなにか他にstylusメリットデメリットがあればコメントしていただけますと幸いです!
- 投稿日:2021-03-21T21:39:19+09:00
Vue.jsにおけるコンポーネントの基本
Vue.jsのコンポーネントの基本
Vue.jsにおけるコンポーネントについて解説していきます。
コンポーネントとは
コンポーネントとはWebサイトを構成する部品を分割し、部品ごとにソースコード(HTML、CSS、JavaScriptなど)にまとめ一つのモジュールにすることをいいます。
大抵のWebサイトには検索フォーム、ナビゲーション、ヘッダー、フッター、サイドバーに分かれていますが、ページ毎に共通のソースコードがあり、同じ内容を、毎回記載していくのはあまり効率が良くありません。
そこで、各パーツ毎にソースコードをまとめて共通化する事で、効率よく開発をすすめることができます。
Vue.jsでは、このコンポーネントという機能を使い、パーツを共通化しています。
コンポーネントの作成方法
コンポーネントを作成する為には、Vue.component()メゾットを使い、コンポーネントを登録する必要があります。
Vue.component()メゾットを使用することで、グローバルスコープにコンポーネントを登録することになり、以後どこからでもコンポーネントにアクセスすることができます。main.jsVue.component('コンポーネント名',{ template : HTMLの内容, })
コンポーネント名にはコンポーネントとして使用したい名前を自由に決めることがでます。
template以降は表示したい内容をHTMLを利用して記載します。
例えば、hello-world.js内のtemplateにHello-Worldと記述したとしましょう。
main.jsVue.component('hello-world, { template : '<h1>Hello-World</h1>' }')
templateの内容を表示させるために、親となるコンポーネントや他のコンポーネントにVue.component()メゾット内で登録したコンポーネント名を利用して、と記述します。
index.html<div id="app"> <hello-world><hello-world> </div> <script src="https://jp.vuejs.org/js/vue.js"></script> <script src="hello-world.js"></script> <script src="main.js"></script>
main.jsにDOMノードを結びつける記述をします。
main.jsvar app = new Vue({ el:'#app' })表示結果
Hello-Worldこの様にして、内に記載された内容をコンポーネントのtemplateオプションを使用し描写することができます。
【注意点】
Vue.component()メゾットはnew Vue()メゾットより先に記述する必要があります。その為、コンポーネントを定義したファイル(ここではHello-World.js)はnew Vue()を記述したファイル(main.js)よりも先にHTMLに読み込む必要があります。Vue.component()の構成
Vue.component()メゾットの第二引数はオブジェクト形式で、templateオプションだけでなく、dataやmethodsなど様々なオプションを追加することができます。
main.jsVue.component('button-counter',{ template: '<p>カウント:{{ count }} <button v-on:click="countUp">Up</button><button v-on:click="countDown">Down</button></p>', data: function(){ return { count : 0 } }, methods: { countUp: function(){ this.count++ }, countDown: function(){ this.count-- }, } })htmlファイルにはbutton-counterタグを追加することで、カウンターを作成することができます。
Vue.component()のtemplateオプションに指定するテンプレートは、必ず全体を単一のタグで囲む必要があり、複数のタグで囲むとエラーが発生します。
main.jsVue.component('hello-world',{ template: <div><p>Hello-World</p><p>I'm Masaki</p></div> })
templateタグはテンプレート全体を ` で囲むと改行することができます。main.jsVue.component('hello-world',{ template: ` <div> <p>Hello-World</p><p>I'm Masaki</p> </div>` })ローカルスコープの登録
コンポーネントの登録にはグローバルスコープとローカルスコープの2つの方法があります。
Vue.component()メゾットを使用する場合はグローバルコンポーネントとなり、どこからでもコンポーネントを利用することができます。
一方でコンポーネントをローカルに登録することで、ある特定のコンポーネント以外は、参照することができないように制限をかけることができます。
ローカルへの登録は、親コンポーネントのcomponentオプションに子コンポーネントを定義することでローカルスコープとなります。
子コンポーネントにコンポーネントを定義します。
main.jsvar my-component = { template: '<p>{{message}}</p>', data: function(){ return{ message: '子コンポーネントです。' } } };
親コンポーネントから、子コンポーネントのcomponentオプションに定義されているmy-componentというプロパティ名を関連づけます。
ローカルスコープで登録したコンポーネントは、親コンポーネントのテンプレート内でしか使用することができません。index.html<div id="app"> <my-component></my-component> </div>コンポーネント間のデータの受け渡し(親コンポーネント ⇒ 子コンポーネント)
先に説明した親コンポーネントと子コンポーネントの連携は、コンポーネント間でデータを受け渡しができるようすることで、実施することができます。
親コンポーネントから子コンポーネントへデータを受け渡すためにはpropsオプションを子コンポーネントを定義します。
main.jsVue.component('hello-world',{ template: ' <h1> {{ message }} </h1>', props: ['message'] })
次に親のテンプレートで子コンポーネントのタグ属性(ここではhello-world)を追加します。index.html<div id="#app"> <hello-world message="hello-world"></hello-world> </div>このように子コンポーネントのpropsに定義しているプロパティと同じ名前を使用し、message属性のデータが親コンポーネントから子コンポーネントに渡されることになります。
コンポーネント間のデータの受け渡し(子コンポーネント ⇒ 親コンポーネント)
子から親へデータを渡す場合、$emitメゾットを利用して、データの受け渡しを行います。
下記例は子コンポーネント側のボタンに対して、クリックイベントを設定してます。
子の$emit()メゾットを実行して、child-clickイベントを発生させ、親のイベントハンドラが呼び出されます。main.jsVue.component('my-vue', { template:` <div> <button :click="click">値下げする。</button> </div>`, props: ['price'], methods: { click: function(){ this.$emit('child-click'); } } });
index.html<div id="app"> <my-vue :child-click="priceDown" v-bind:price="price"></my-vue> </div>
最後に親にイベントハンドラ(ここではpriceDOwn)を定義して、子の値下げボタンをクリックするたびに、親のpriceが100円ずつ減少するように設定します。parent.jsvar app = new Vue ({ el:'#app', data: { price: 1000 }, methods:{ priceDown: function(){ this.price -=100; } } })以上です。
- 投稿日:2021-03-21T21:14:42+09:00
BuefyからPrimeVueに移行
vueが3.0になり、そろそろ移行しようかなーと思ってやってみると、普段使っていたBuefyが3.0に未対応のようです。
開発者の方々?はPrimeVueで忙しい的なことを書いていたので移行を検討。
vue3.0に対応しているし、Buefyで使っていたコントロールはほぼ網羅している。
- 投稿日:2021-03-21T20:20:12+09:00
シンプルで使いやすいJSONフォーマットツールを作ってみた
はじめに
こんにちは、システム開発チーム「presto」です。
開発業務(プログラミング中)に「こんなツール、あんなツールが身近なところにあったらいいなぁ」と思ったことはありませんか?ここではそんなツールをVuetifyを使って作ってみたので紹介させてください。
JSONフォーマットツール
今回紹介させていただくのは、JSONフォーマットツールです。
画面は以下の通りです。
使い方
使い方は以下の通りです。
1. 画面上部のテキストエリアにJSON文字列を入力する
2. (任意)フォーマットのインデント数を指定する
3. 結果確認1. 画面上部のテキストエリアにJSON文字列を入力する
例として、以下を入力します。
{"number":12345,"result":true}2. (任意)フォーマットのインデント数を指定する
デフォルトのインデント数は「4」となっています。
3. 結果確認
以下のような値が表示されます。
{ "number": 12345, "result": true }インデント数を「0」と指定すると、整形後のJSON文字列がMinifyされます。
{"number":12345,"result":true}JSONフォーマットできない文字列を入力した場合
JSONフォーマットできない文字列を入力した場合、SyntaxErrorが表示されます。
SyntaxError: Unexpected token d in JSON at position 6まとめ
今回は、JSONフォーマットツールの紹介をさせていただきました。
今後もよろしくお願いします。
ありがとうございましたー!
- 投稿日:2021-03-21T19:17:58+09:00
Docker で Vue.js + Spring REST をやってみる
想定する対象
- Docker の初学者
- Vue.js の初学者
- Spring MVC の初学者
目的
- Docker について初歩的な動作を実践してみます
- Vue.js について初歩的な動作を実践してみます
- Spring MVC について初歩的な動作を実践してみます
前提
- Docker, Docker Compose が使用可能
$ docker --version Docker version 20.10.5, build 55c4c88$ docker-compose --version docker-compose version 1.28.5, build c4eb3a1f
- JDK, Maven が使用可能
$ java -version java version "1.8.0_202" Java(TM) SE Runtime Environment (build 1.8.0_202-b08) Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)$ mvn -version Apache Maven 3.6.1 (d66c9c0b3152b2e69ee9bac180bb8fcc8e6af555; 2019-04-05T04:00:29+09:00)ゴール
- アプリケーション の仕様
No URL Docker イメージ 概要・用途 実装技術 備考 1 http://localhost:8880/index.html nginx フロントエンド HTML, Javascript(Vue.js), css WEBコンテンツを提供します 2 http://localhost:8881/api/message jetty バックエンド Java(Spring MVC) APIを提供します ※今回は RDBMS 部分は省略 目次
- Docker を使ってみる
- HTML ファイルを表示させる
- 配布可能な Docker アプリを作成する(※簡易版)
- Vue.js を導入する
- Ajax リクエスト処理の追加
- バックエンドサーバーの構築
- Java WEBアプリの作成
- CSS フレームワークを適用
Docker を使ってみる
- 既存の Docker イメージを使ってみます
Docker イメージの取得
- nginx Docker イメージを取得します
$ docker pull nginx:latest
- 取得済みのイメージを確認します
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 6084105296a9 7 days ago 133MB
コンテナの作成と実行
- コンテナを作成して実行します
$ docker run --name nginx01 -d -p 8880:80 nginx:latest
- WEBブラウザで確認します
http://localhost:8880/ ※ Nginx コンテナが立ち上がっています
- 同じイメージから別の nginx コンテナを立てることも出来ます ※ポートを別にします
$ docker run --name nginx02 -d -p 7770:80 nginx:latest
- WEBブラウザで確認します
http://localhost:7770/ ※ Nginx コンテナが立ち上がっていますコンテナの停止と削除
- コンテナの停止
$ docker stop nginx01 $ docker stop nginx02
- コンテナの再実行
$ docker start nginx01
- コンテナの状態確認
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 29f2e4e799c7 nginx:latest "/docker-entrypoint.…" 3 hours ago Up 6 seconds 0.0.0.0:8880->80/tcp nginx01
- コンテナの削除
$ docker rm nginx01 $ docker rm nginx02
- コンテナの状態確認(※停止したコンテナ含む)
$ docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESDocker イメージの削除
- Docker イメージの確認
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE nginx latest 6084105296a9 7 days ago 133MB
- Docker イメージの削除
$ docker rmi nginx:latest
- Docker イメージの再確認
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE
<ここまでのまとめ>
- 既に公開されている nginx の Docker イメージ を使用することが出来ます
- nginx イメージ から複数の異なる設定の nginx コンテナを立ち上げることが出来ます
- コンテナを起動、停止、削除することが出来ます
- nginx イメージを削除することが出来ます
- ここまでは Docker 環境の中で実行可能です
HTML ファイルを表示させる
- アプリ開発の為の特定のディレクトリが必要になります、以下を開発ディレクトリとしました
C:\Users\someone\Desktop\app01※以降、各自の環境で読み替えて下さい
- 作成するファイルの概要
No ファイルPATH 拡張子 記述言語 概要・内容 1 var\www\html\index.html html HTML WEBコンテンツ 2 docker-compose.yml yml YML Docker 起動用設定 HTML コンテンツの作成
- HTMLファイルの作成
※以下のようにファイルを作成します※アプリディレクトリ/var/www/html/index.html※アプリディレクトリで $ mkdir -p var/www/html $ touch var/www/html/index.html
- index.html の内容
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> <div>It works!</div> </body> </html>Docker Compose の設定ファイルの作成
- docker-compose.yml の作成
※以下のようにファイルを作成します※アプリディレクトリで $ touch docker-compose.yml
- docker-compose.yml の内容
version: '3' services: nginx: image: nginx:latest ports: - "8880:80" volumes: - ./var/www/html:/usr/share/nginx/html container_name: nginx01
- Docker Compose を実行します
※アプリディレクトリで $ docker-compose up -d Creating nginx01 ... done
- WEBブラウザで確認します
http://localhost:8880/ ※ It works! とWEBブラウザに表示されます
- <ポイント>
docker-compose.yml
のvolumes:
の項目でローカルPCの HTML コンテンツを nginx コンテナに渡して(マウントして)います- 上記の
docker-compose.yml
は以下のdocker run
コマンドとほぼ同義です$ docker run -v "C:\Users\someone\Desktop\app01\var\www\html:/usr/share/nginx/html" --name nginx01 -d -p 8880:80 nginx:latest<ここまでのまとめ>
- Docker 環境があればローカルのHTMLファイルを手軽に表示することが出来ます
- 既存の nginx イメージを利用する場合 Dockerfile は必要ありません
docker-compose.yml
は複雑なパラメータを持つdocker run
コマンドの代用となります
配布可能な Docker アプリを作成する(※簡易版)
app01
フォルダを丸ごと zip 圧縮なりして配布します- zip ファイルを解凍し
docker-compose.yml
が存在するディレクトリでdocker-compose up
します- もちろん相手側の環境に Docker 環境がインストールされている必要があります
<ここまでのまとめ>
- Docker コンテナとは Docker 環境で実行出来るアプリケーションといえます
- Docker イメージとは Docker コンテナを作成するテンプレートとして使用されます
- 静的WEBコンテンツを表示する場合、今回の設定例ですとアプリディレクトリの
/var/www/html
配下にWEBコンテンツ(html, js, css, jpg, png, etc)を配置すれば手軽に nginx コンテナを立ち上げて確認することが出来ます
Vue.js を導入する
- 作成・修正するファイルの概要
No ファイルPATH 拡張子 記述言語 概要・内容 1 var\www\html\index.html html HTML WEBコンテンツ 2 var\www\html\scripts\app.js js javascript WEBコンテンツ・コードビハインド テキストの表示
- index.html の修正
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="/scripts/app.js" language="javascript" type="text/javascript"></script> <title>index</title> </head> <body> <div id="vue-app"> <div>{{ message }}</div> </div> </body> </html>
- この段階でWEBブラウザで表示してみます
http://localhost:8880/ ※ {{ message }} とWEBブラウザに表示されます ※ この状態では vue の処理が走っていません
- app.js の作成
※以下のようにファイルを作成します※アプリディレクトリ/var/www/html/scripts/app.js※アプリディレクトリで $ mkdir -p var/www/html/scripts $ touch var/www/html/scripts/app.js
- app.js の内容
// データオブジェクト var data = { message: "It works!" }; window.addEventListener("load", () => { // Vue インスタンスにオブジェクトを追加する var vm = new Vue({ el: '#vue-app', data: data }) });
- WEBブラウザで確認します
http://localhost:8880/ ※ It works! とWEBブラウザに表示されます
- <ポイント>
app.js
で javascript のdata
オブジェクトを Vue.js のリアクティブシステムdata
に設定しています- その結果
data
オブジェクトのmessage
プロパティにをindex.html
に変数名message
としてバインドすることが出来ます※参考: Vue のリアクティブシステムとは?
index.html
をWEBブラウザ(Chrome)などで表示した状態で
- Developer Tools(F12) を開きます
- Console のタブに以下のように入力します
> data.messege="テスト" ※ エンターを押します ※ ページの表示にも "テスト" が反映されています
- <ポイント>
- javascript の
data
オブジェクトが Vue のリアクティブシステムに設定されているため自動的にHTML側の表示が更新されます
- このような動作をリアクティブと呼びます
ボタンの追加
- index.html の修正
※以下のようにファイルを修正します<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="/scripts/app.js" language="javascript" type="text/javascript"></script> <title>index</title> </head> <body> <div id="vue-app"> <div>{{ message }}</div> <button v-on:click="() => { message = 'Button Clicked!' }">Click</button> </div> </body> </html>
- WEBブラウザで確認します
http://localhost:8880/ ※ [Call]ボタンを押すと Button Clicked! とWEBブラウザに表示されます
- <ポイント>
index.html
に追加したボタンに Vue のv-on:click
ディレクティブを設定しています- この例ではボタンをクリックした時に実行される javascript の
関数
を直接記述していますmessage
は Vue に設定したdata.message
が参照されています- Vue のリアクティブシステムにより HTML側の
message
も自動的に更新されますイベントメソッドの実装
- index.html の修正
※以下のようにファイルを修正します<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="/scripts/app.js" language="javascript" type="text/javascript"></script> <title>index</title> </head> <body> <div id="vue-app"> <div>{{ message }}</div> <button v-on:click="getMessage">Click</button> </div> </body> </html>
- WEBブラウザで確認します
http://localhost:8880/ ※ 真っ白(エラー)になります 'getMessage' メソッドを記述していないからです関数を記述してみる(※参考)
- app.js の修正
※以下のように修正してみます// データオブジェクト var data = { message: "It works!" }; // 関数(オブジェクト) var getMessage = () => { message = 'Button Clicked!' }; window.addEventListener("load", () => { // Vue インスタンスにオブジェクトを追加する var vm = new Vue({ el: '#vue-app', data: data }) });
- WEBブラウザで確認します
http://localhost:8880/ ※ [Call]ボタンを押しても何も変化がありません
- さらに app.js の修正
※以下のように修正してみます// データオブジェクト var data = { message: "It works!" }; // 関数(オブジェクト) var getMessage = (event) => { event.view.data.message = 'Button Clicked!' }; window.addEventListener("load", () => { // Vue インスタンスにオブジェクトを追加する var vm = new Vue({ el: '#vue-app', data: data }) });
- WEBブラウザで確認します
http://localhost:8880/ ※ [Call]ボタンを押すと Button Clicked! とWEBブラウザに表示されます
- <ポイント>
- Vue の
v-on:click
ディレクティブから javascript の関数(オブジェクト)を呼び出せます- その際、イベント引数が暗黙的に渡って来ます
- 【注意!】この方法は Vue の一般的な流儀ではありません、通常は次に説明する方法でイベントハンドラを記述します
- 動作の理解のために説明しました
イベントハンドラを記述する(※実験版)
- app.js の修正
※以下のように修正します// データオブジェクト var data = { message: "It works!" } window.addEventListener("load", () => { // Vue インスタンスにオブジェクトを追加する var vm = new Vue({ el: '#vue-app', data: data, // `methods` オブジェクトの下にメソッドを定義する methods: { getMessage: () => { data.message = 'Button Clicked!'; } } }) });
- WEBブラウザで確認します
http://localhost:8880/ ※ [Call]ボタンを押すと Button Clicked! とWEBブラウザに表示されます
- <ポイント>
- Vue インスタンスに
methods
オブジェクトを追加し、getMessage
関数(オブジェクト)を記述します- HTMLページ
message
へのアクセスがdata.message
になる点に注意してください- これは グローバルなオブジェクト
var data
を参照しています- 【注意!】この方法は Vue の一般的な流儀ではありません、通常は次に説明する方法でイベントハンドラを記述します
- 動作の理解のために説明しました
イベントハンドラを記述する(※Vueの流儀)
- app.js の修正
※以下のように修正します// データオブジェクト var data = { message: "It works!" } window.addEventListener("load", () => { // Vue インスタンスにオブジェクトを追加する var vm = new Vue({ el: '#vue-app', data: data, // `methods` オブジェクトの下にメソッドを定義する methods: { getMessage() { this.message = 'Button Clicked!'; } } }) });
- WEBブラウザで確認します
http://localhost:8880/ ※ [Call]ボタンを押すと Button Clicked! とWEBブラウザに表示されます
- <ポイント>
- 今回
getMessage
メソッドはfunction
を使用しない省略記法で記述しています- この記法だとイベントハンドラ内で
this
を参照可能です- ※ Vue の
data
オブジェクトのmessage
プロパティをthis.message
で参照出来るのが奇妙に見えますが Vue の流儀なので慣れるしかないと思います<ここまでのまとめ>
- このようにシンプルかつ最小限の方法で WEBコンテンツに Vue.js を導入することが出来ます
- Vue.js を使用することによりWEBコンテンツの
ビュー定義
と実装コード
を分離することが可能となります- また様々な方法で javascript からオブジェクト変数値を直接操作しても Vue のリアクティブ処理が破綻しないことが分かります
Ajax リクエスト処理の追加
HTMLファイルに Ajax リクエスト処理を追加します (axios "アクシオス" 使用)
API の仕様
No URL data 要素 データ型 内容 1 http://localhost:8881/api/message massage 文字列 メッセージを返します 2 http://localhost:8881/api/message error bool値 APIのエラー状況を返します
- 取得する JSON の例
{ "message": "some message", "error": false }
- index.html の修正
※以下のように修正します<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script src="/scripts/app.js" language="javascript" type="text/javascript"></script> <title>index</title> </head> <body> <div id="vue-app"> <div>{{ message }}</div> <button v-on:click="getMessage">Call</button> </div> </body> </html>
- app.jsの修正
※以下のように修正します// データオブジェクト var data = { message: "It works!" } window.addEventListener("load", () => { // Vue インスタンスにオブジェクトを追加する var vm = new Vue({ el: '#vue-app', data: data, // `methods` オブジェクトの下にメソッドを定義する methods: { getMessage() { // バックエンドサーバーへ Ajax リクエスト axios.get( 'http://localhost:8881/api/message', { headers: {'content-type': 'application/json'} }) .then(response => { // Ajax リクエストの戻り値を取得 this.message = response.data.message; }) .catch(error => { // エラーメッセージを表示 this.message = error; }); } } }) });
- WEBブラウザで確認します
http://localhost:8880/ ※ [Call]ボタンを押すと Error: Network Error とWEBブラウザに表示されます ※ API リクエストを受けるバックエンドサーバーの準備が出来ていないからです
- <ポイント>
getMessage
メソッドから Ajax通信でhttp://localhost:8881/api/message
をリクエストしています- エラーが発生した場合には
message
に内容を表示します<ここまでのまとめ>
- axios ライブラリを使用してバックエンドサーバーに API リクエストを投げることが出来そうです
- 今回の例では厳密な REST API ではありません、単に HTTP リクエスト GET で JSON を取得します
バックエンドサーバーの構築
- 修正するファイルの概要
No ファイルPATH 拡張子 記述言語 概要・内容 1 docker-compose.yml yml YML Docker 起動 Jetty コンテナの追加
Jetty は JVM系言語の WEBアプリをデプロイする WEBサーバーです
- ここでは 8881 ポートで起動させます
docker-compose.yml の修正
※以下のようにファイルを修正しますversion: '3' services: nginx: image: nginx:latest ports: - "8880:80" volumes: - ./var/www/html:/usr/share/nginx/html container_name: nginx01 jetty: image: jetty:9.4.38 ports: - "8881:8080" volumes: - ./target:/var/lib/jetty/webapps container_name: jetty01
- Docker Compose を実行します
※アプリディレクトリで $ docker-compose up -d Creating jetty01 ... done
- WEBブラウザで確認します
http://localhost:8881/ ※ Error 404 - Not Found. とWEBブラウザに表示されますが Jetty コンテナは立ち上がっています<ここまでのまとめ>
- JVM系 WEBアプリ実行環境 Jetty も
docker-compose.yml
の設定だけでコンテナが立ち上がります
Java WEBアプリの作成
- JVM系のWEBアプリは通常 拡張子
.war
形式の zipファイルとして作成されます- Jetty の場合
/var/lib/jetty/webapps
ディレクトリに*.war
ファイルを配置することで自動的にデプロイされます- ローカルPCの
*.war
WEBアプリをdocker-compose.yml
のjetty
のvolumes
設定でコンテナに配置します
- ここでは
./target
ディレクトリに.war
ファイルをビルド出力すれば、自動的にコンテナにデプロイされますwar アプリの作成
- 作成するファイルの概要
No ファイルPATH 拡張子 記述言語 概要・内容 1 pom.xml xml XML Maven ビルド設定 2 src\main\webapp\WEB-INF\web.xml xml XML サーブレット定義 3 src\main\webapp\WEB-INF\spring\app-config.xml xml XML Spring bean定義 4 src\main\webapp\WEB-INF\spring\mvc-config.xml xml XML Spring bean定義 5 src\main\java\org\example\controller\ApiController.java java Java コントローラクラス 6 src\main\java\org\example\response\Response.java java Java レスポンスクラス
- 必要なファイルをまとめて作成します
※アプリディレクトリで $ mkdir -p src/main/webapp/WEB-INF/spring $ mkdir -p src/main/java/org/example/controller $ mkdir -p src/main/java/org/example/response $ touch pom.xml $ touch src/main/webapp/WEB-INF/web.xml $ touch src/main/webapp/WEB-INF/spring/app-config.xml $ touch src/main/webapp/WEB-INF/spring/mvc-config.xml $ touch src/main/java/org/example/controller/ApiController.java $ touch src/main/java/org/example/response/Response.javaxml 設定ファイルの作成
Maven の設定
- Maven ビルド設定である pom.xml の作成
※以下のようにファイルを作成します※アプリディレクトリ/pom.xml
- pom.xml の内容
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>minimum-api</artifactId> <packaging>war</packaging> <version>1.0.0</version> <name>webapp</name> <properties> <jackson.version>2.11.0</jackson.version> <spring.version>5.2.6.RELEASE</spring.version> <lombok.version>1.18.12</lombok.version> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <!-- Jackson --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>${jackson.version}</version> </dependency> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <scope>provided</scope> </dependency> </dependencies> <pluginRepositories> <pluginRepository> <id>central</id> <name>Central Repository</name> <url>https://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> <releases> <updatePolicy>never</updatePolicy> </releases> </pluginRepository> </pluginRepositories> <repositories> <repository> <id>central</id> <name>Central Repository</name> <url>https://repo.maven.apache.org/maven2</url> <layout>default</layout> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <build> <finalName>api</finalName> <plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>${java.version}</source> <target>${java.version}</target> <encoding>${project.build.sourceEncoding}</encoding> </configuration> </plugin> </plugins> </build> </project>
- <ポイント>
dependencies
セクションで Jackson(JSOMマッパー), Spring, Lombok の jarライブラリをダウンロードしますpluginRepositories
,repositories
セクションで Maven Central Repository の設定を追加します(※HTTPS対策)build
セクションで .war ファイルへビルドする設定を追加します
<finalName>api</finalName>
と設定することによりapi.war
というファイル名で./target
ディレクトリに出力されます$ mvn install
コマンドでビルドします (※maven-compiler-plugin が良きに実行してくれます)./target
ディレクトリにapi.war
が出力されることにより Docker Jetty コンテナ側の/var/lib/jetty/webapps
にコピーされ自動的に Docker コンテナ側の war アプリも更新されます
- ※実稼働を想定する場合にはこの方法が唯一のデプロイ方法ではないです
Servlet の設定
Servlet とは Java で HTTPリクエスト・レスポンスを扱う Java EE の標準仕様の一部分です
Jetty
やTomcat
は Servlet API の標準仕様を実装した Java の WEBサーバーです
Jetty
やTomcat
は Servlet API の実装なので RDBMS へアクセスする機能などはありません- RDBMS へアクセスする場合には JPA という標準仕様があり
Hibernate
などが JPA を実装します- Java EE 標準仕様のリファレンス実装として全部入りの
GlassFish
というアプリケーションサーバソフトがありますServlet の設定である web.xml の作成
※以下のようにファイルを作成します※アプリディレクトリ/src/main/webapp/WEB-INF/web.xml
- web.xml の内容
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app__3__1.xsd" version="3.1"> <display-name>minimum-api</display-name> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/app-config.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/spring/mvc-config.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> </web-app>
- <ポイント>
org.springframework.web.servlet.DispatcherServlet
が Spring Framework として動作するServlet
になります
- Spring Framework では基本的に全てのリクエスト・レスポンスが
DispatcherServlet
で行われます- また Spring Framework とは関係ない独自の
Servlet
をweb.xml
に設定することも可能です
- 超レガシーな Java WEB システムでは1ページのHTMLに一つの
Servlet
が設定されたケースもあると思いますSpring の設定
- Spring の設定である app-config.xml, mvc-config.xml の作成
※以下のようにファイルを作成します※アプリディレクトリ/src/main/webapp/WEB-INF/spring/app-config.xml ※アプリディレクトリ/src/main/webapp/WEB-INF/spring/mvc-config.xml
- app-config.xml の内容
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example"/> </beans>
- mvc-config.xml の内容
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <context:component-scan base-package="org.example"/> <mvc:annotation-driven /> </beans>
- <ポイント>
component-scan base-package
でorg.example
パッケージのアノテーション設定を読み込んでいますmvc:annotation-driven
で Spring MVC におけるアノテーション設定による動作を有効にしています- この例では
app-config.xml
,mvc-config.xml
の二つの XML ファイルを設定しています
- 用途によって分けていますが、必ずしもこの方法でないと設定が読み込めないわけではないです
java コードの作成
- コントローラクラスとレスポンスクラスを作成します
※以下のようにファイルを作成します※アプリディレクトリ/src/main/java/org/example/controller/ApiController.java ※アプリディレクトリ/src/main/java/org/example/response/Response.javaコントローラー POJO クラスの実装
- ApiController.java の内容
package org.example.controller; import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.example.response.Response; /** * Ajax通信で HTTP リクエストされる Controller POJO クラス */ @RequiredArgsConstructor @Controller public class ApiController { /////////////////////////////////////////////////////////////////////////// // Field @NonNull private ApplicationContext context; // lombok が自動でコンストラクタをつくりそこからインジェクションされます /////////////////////////////////////////////////////////////////////////// // public methods // Ajax リクエストを受けメッセージを返します @CrossOrigin @RequestMapping( value="/message", method=RequestMethod.GET, headers="Accept=application/json" ) public @ResponseBody Response getMessage() { // 本来ならここでビジネスロジックを処理して // レスポンス用のオブジェクトを返す return (Response) context.getBean( "response", // bean の名前を指定 "Did you call me?", // メッセージの内容 false ); } }
- <ポイント>
@RequiredArgsConstructor
アノテーションで lombok が自動で必要なコンストラクタをコンパイル時に自動生成します@Controller
アノテーションで Spring MVC のコントローラとして動作します@CrossOrigin
アノテーションで他サイトからのアクセスを許可します(※Cross-Origin Resource Sharing)@RequestMapping
アノテーションで HTTPリクエストを受けます@ResponseBody
アノテーションで HTTPレスポンスを返します
- この例では
Jackson
ライブラリで POJO を JSON にマッピングしていますレスポンス POJO クラスの実装
- Response.java の内容
package org.example.response; import lombok.Getter; import lombok.NonNull; import lombok.AllArgsConstructor; import lombok.EqualsAndHashCode; import lombok.ToString; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; /** * JSON にマッピングされる POJO クラス */ @Getter @AllArgsConstructor @EqualsAndHashCode @ToString @Component(value="response") // bean の名前 @Scope(value="prototype") public class Response { /////////////////////////////////////////////////////////////////////////// // Field @NonNull private String message; // メッセージの内容 @NonNull private boolean isError; // エラーが発生したかどうか(※ダミー) }
- <ポイント>
@Getter
,@AllArgsConstructor
アノテーション等は lombok が自動で必要なメソッドをコンパイル時に自動生成します
- lombok ライブラリを使用することでボイラーコードの記述を回避でき、見通しの良いコードを書くことが可能です
@Component
アノテーションで bean 名response
として Spring Framework に登録します@Scope
アノテーションでprototype
と指定することにより DI から new 相当でオブジェクトを取得します
- ※ Spring Framework のデフォルト動作では DI で取得するオブジェクトはシングルトンです
java WEBアプリのビルド
ビルドツールとして Maven (メイブン) を使用します
- 基本的に
pom.xml
が存在するディレクトリでmvn install
コマンドを走らせるとJavaアプリがビルドされますビルドコマンド を実行します
※アプリディレクトリで $ mvn install [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.041 s [INFO] Finished at: 2021-03-21T09:23:45+09:00 [INFO] ------------------------------------------------------------------------
- WEBブラウザで確認します
http://localhost:8880/index.html ※ [Call]ボタンを押すと Did you call me? とWEBブラウザに表示されます
<ここまでのまとめ>
- フロント側のWEBコンテンツから、バックエンドにAPIリクエストすることが出来ました
- またバックエンド側は、フロントのAPIリクエストに応答することが出来ました
CSS フレームワークを適用
CSS フレームワークとして UIkit を使用します
index.html の修正
※以下のようにファイルを修正します<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <script src="https://cdn.jsdelivr.net/npm/vue@2.6.12"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.6.18/dist/css/uikit.min.css" /> <script src="https://cdn.jsdelivr.net/npm/uikit@3.6.18/dist/js/uikit.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/uikit@3.6.18/dist/js/uikit-icons.min.js"></script> <script src="/scripts/app.js" language="javascript" type="text/javascript"></script> <title>index</title> </head> <body> <div id="vue-app"> <div class="uk-section uk-section-small uk-section-secondary"></div> <div class="uk-section uk-section-primary"> <div class="uk-container uk-container-small"> <h1 class="uk-heading-large">Message</h1> <p> <span uk-icon="icon: info"></span> <span class="uk-text-lead">{{ message }}</span> </p> <p> <button class="uk-button uk-button-large uk-button-primary" v-on:click="getMessage">Call</button> </p> </div> </div> <div class="uk-section uk-section-xlarge uk-section-muted"></div> </div> </body> </html>
- WEBブラウザで確認します
http://localhost:8880/index.html<ここまでのまとめ>
- CSS フレームワークの導入で見た目を整えることが出来ます
感想
- バックエンド側の実装を、フロント側からのAPIリクエストに対するJSON応答に限定することにより、システムが疎結合に保たれると思いました
- バックエンド側で JSP(HTML) のようなフロント部分を提供しない仕組みは、フロント側がスマホネイティブアプリや Unity ゲームなどの場合にはそれらに対して汎用的に利用可能となると思いました
今後の展開は?
- フロント側の javascript を TypeScript に置き換えると良いかも知れません
- webpack の設定が必要になります
- バックエンド側の処理に RDBMS を使用する設定を追加すると良いと思います
- Java を使用する理由の一つに RDBMS に対する処理が実用的に書けることがあります
- JPA 仕様を実装する
Hibernate
等のライブラリが必要となりますがpom.xml
Jetty コンテナに同梱出来ます- この場合に必要となるのは
MySQL
,PostgreSQL
の Docker コンテナとなります
リポジトリ
ありがとうございました
- 投稿日:2021-03-21T17:46:36+09:00
Nuxt.jsでフロント側でフォームにバリデーションをつけよう!
概要
Nuxt.jsを使用しformにフロント側でバリデーションをつけていきます。
前提知識
html, css, Vue.jsの基本的な知識
プロジェクトの作成
今回はデスクトップに作成していきます
cd ~/Desctop npx create-nuxt-app validate npm run devこちらが立ち上げれば環境構築は成功です。
touch pages/form.vue今回は以下のようなフォームを作成していきます。
項目 バリデーション バリデーション 名前 必ず必要 8文字以内 会社名 必ず必要 pages/form.vue<template> <form> <label>名前</label> <input type="text" v-model="form.name.val"> <p>{{form.name.val}}</p> <label>会社名</label> <input type="text" v-model="form.company.val"> </form> </template> <script> export default { data() { return { form: { name: { label: "名前", val: null, errorMessage: null }, company: { label: "会社名", val: null, errorMessage: null }, }, } }, } </script>まずはこんな感じに簡単に記述していきます。
説明するとまずは適当にフォームを作成します。
scriptタグの中身は
dataでformのデータを作成し、その中にnameとcompanyを作っています。v-modelでそれぞれの入力された値を受け取っています。
labelはそれぞれのinputの名前を選択しておきます。
valとerrorMessageを作成しデフォルト値をnullにします。バリデーションを追加していく
バリデーションのメソッドを追加していきます。
pages/form.vue<script> export default { data() { return { form: { name: { label: "名前", val: null, errorMessage: null }, company: { label: "会社名", val: null, errorMessage: null }, }, } }, methods: { validateName() { const { name } = this.form const maxLength = 8 if(!name.val) { name.errorMessage = `${name.label}は必須項目です` return } if (name.val.length > maxLength) { name.errorMessage = `${name.label}は${maxLength}以内で入力してください` return } name.errorMessage = null }, validateCompany() { const { company } = this.form if (!company.val) { company.errorMessage = `${company.label}は必須項目です` return } company.errorMessage = null }, onSubmit() { this.validateName() this.validateCompany() } } } </script>scriptタグをこちらに変更します!
説明します。
バリデーションを
validateName, validateCompanyメソッドで追加してます。const {name} = this.formでまず代入をし
8文字以内の設定なのでmaxLengthを8に設定します。
あとはif分で条件を出していくだけなのですが、${}
のテンプレートリテラルを利用して条件に一致すればnullのエラ〜メッセージを書き換えていきます。
必ずreturnで処理を終わらせるのを忘れずに!最後にonSubmitでこれらのメソッドを呼び出します。
あとはどのタイミングでエラ〜メッセージを出すか自分で決めるだけです。エラ〜メッセージを反応させる
今回はエラ〜メッセージを出すをゴールにしているので、デザインはほぼ行いません。
pages/form.vue<template> <div> <form @submit.prevent="onSubmit"> <label>名前</label> <input @keyup="validateName" :class="{'border-red':form.name.errorMessage}" type="text" v-model="form.name.val"> <label>会社名</label> <input @keyup="validateCompany" :class="{'border-red':form.company.errorMessage}" type="text" v-model="form.company.val"> <input type="submit"> </form> <p class="errorMessage">{{form.name.errorMessage}}</p> <p class="errorMessage">{{form.company.errorMessage}}</p> </div> </template> <script> export default { data() { return { form: { name: { label: "名前", val: null, errorMessage: null }, company: { label: "会社名", val: null, errorMessage: null }, }, } }, methods: { validateName() { const { name } = this.form const maxLength = 8 if(!name.val) { name.errorMessage = `${name.label}は必須項目です` return } if (name.val.length > maxLength) { name.errorMessage = `${name.label}は${maxLength}以内で入力してください` return } name.errorMessage = null }, validateCompany() { const { company } = this.form if (!company.val) { company.errorMessage = `${company.label}は必須項目です` return } company.errorMessage = null }, onSubmit() { this.validateName() this.validateCompany() } } } </script> <style> .errorMessage { color: red; } .border-red { border: 1px solid red; } </style>こちら最終的なコードです。
注目すべき点だけ説明していきます。
@submit.prevent="onSubmit"でまずバリデーションを呼び出します。
@submithじゃv-on:submitの省略記法で、つまりフォームを送信するタイミングでバリデーションを確認して!
といった意味になります。
preventはsubmitのイベント修飾子でsubmitの動きを止めることができます。
エラ〜メッセージが出たのに、送信されたら意味がないですからね笑submit.preventはよく使うので覚えておきましょう。
あとは@keyupのタイミングでそれぞれのバリデーションを確認しています。
:classはv-bind:classの省略記法で
{クラス : A}
Aがtrueであればクラスを適用するといった意味になります。つまりエラ〜メッセージがあればstyeleを適用するですね。
border: 1px solid redが適用されます。お疲れ様でした!!
- 投稿日:2021-03-21T17:42:54+09:00
【Vue3】外部サイトからの遷移でクエリパラメータを受け取る(Vue Router4)
概要
- Vue3.0系(Vue Router4)で実装するにあたり、他ページから遷移してきた際に、クエリパラーメータを取得する方法
注意点
- 今回は簡潔に書くためにRouter周りもmain.tsに書いているが他に分割した方が望ましい
- セキュリティの観点からは最悪なソースですw あくまで説明用のため
サンプルコード
下記はmain.tsを想定しています.
import { createApp } from 'vue' import { createRouter } from 'vue-router' import Login from './components/templates/LoginTemplate.vue' import App from './App.vue' const router = createRouter({ routes: [ { path: '/', component: App }, { path: '/login', component: Login }, ] }) router.beforeEach((to, from, next) => { // toからqueryを取得できます const auth = to.query.auth if (!auth) { next({ path: '/login', query: { redirect: to.fullPath } }) } }) const app = createApp(App) app.use(router) app.use(Store) app.mount('#app')解説
まずVue Router4では
createRouter
を使ってルーターインスタンスを生成します.const router = createRouter({ routes: [ { path: '/', component: App }, { path: '/login', component: Login }, ] })
beforeEach
はそれぞれの画面に遷移する前に行う処理を記述できます.
下記の処理は、もしクエリパラメータとしてauth
がなければログイン画面にリダイレクトさせるというものです.
詳しい説明は省きますがto
、from
はいずれもRouteLocationNormalized
というオブジェクトです. そのオブジェクトのメンバーの1つとしてquery
があるので、そこから取得をします.router.beforeEach((to, from, next) => { // toからqueryを取得できます const auth = to.query.auth if (!auth) { next({ path: '/login', query: { redirect: to.fullPath } }) } })参考
- API Reference: https://next.router.vuejs.org/api/index.html
あとがき
Vue3.0系はまだまだ参考になる記事が少ないですね。
Vueエンジニアの方々一緒に頑張りましょう〜
- 投稿日:2021-03-21T15:31:55+09:00
【vue.js】Font awesomeのアイコンを並べる【データバインディング】
環境
Vue.js 6.14.9
Vue CLI 4.5.9Font awesomeを利用
Font awesome を Vue.js で使ってみよう
VueでFont awesomeを利用する方法はこちらを参照させていただきました。アイコンを並べる
まず参照通り表示してみました。
About.vue<template> <div class="about"> <font-awesome-icon icon="coffee" /> </div> </template>ここから、次に
v-for
を使って、アイコンを複数並べたいと思います。About.vue<template> <div class="about"> <div class="item" v-for="(data, index) in hoge" :key="index"> <font-awesome-icon :icon="{{data.icon}}" /> </div> </div> </template>scriptも記述していきます。
About.vue<script> export default { data(){ return { hoge: [ {icon: "coffee"}, {icon: "users"} ]}; } }; </script>あれ、エラーが、、、
上記のエラーをみると、
icon="{{data.icon}}"の代わりに、icon="data.icon"を使ってねと書いてあるので、修正します。About.vue<template> <div class="about"> <div class="item" v-for="(data, index) in hoge" :key="index"> <font-awesome-icon :icon="data.icon"/ > </div> </div> </template>これでエラーは解消できました。
- 投稿日:2021-03-21T12:23:26+09:00
【エラー備忘録】npmをインストールする際のEACCESエラー
npmをバージョンを指定してインストールする為、以下のコマンドを実行した。
$ npm install -g @vue/cli@3.5.0するとエラーになる。
npm ERR! code EACCES 以下続く...エラー文末では以下のように表示されている。
npm ERR! permissions of the file and its containing directories, or try running npm ERR! the command again as root/Administrator.そこでsudoをつけて再度実行すると、無事インストールできた。
$ sudo npm install -g @vue/cli@3.5.0念のため確認すると、3.5.0と表示される。
$ vue -V 3.5.0
- 投稿日:2021-03-21T11:53:24+09:00
【Vue.js】コンポーネント
コンポーネントとは
・画面を構成する要素を個々のUIパーツに分割したもの。
・Vue.jsでは個々のコンポーネントがVueインスタンスとなっており、コンポーネントの組み合わせによって画面を作成していく。
・メリット →複数の画面で再生できる。コードの見通しがよくなる。
コンポーネントの作成手順
ここでは一つのjavascriptファイルで記述していく。
・componentを登録する
componentメソッドを使う。
第一引数には任意のコンポーネント名を指定する。
第二引数にはvueインスタンスを生成する際のオプションと同様のものが指定できる。
※vueインスタンス生成時との違い…dataでは必ず関数の戻り値でオブジェクトを指定する。
※コンポーネントの登録は、インスタンスの生成よりも前に記述する。jsVue.component('user-list', { //componentメソッドでcomponentを登録する。 data(){ //dataでは常に関数の戻り値でオブジェクトを設定する。 return { users: [ {id: 1, name: '1ちゃん'}, {id: 2, name: '2ちゃん'}, {id: 3, name: '3ちゃん'}, {id: 4, name: '4ちゃん'}, {id: 5, name: '5ちゃん'} ] } }, template:` <ul> <li v-for="user in users" :key="user.id"> {{ user.name }} </li> </ul> ` }) const vm = new Vue({ el: '#app', })html<script src="https://unpkg.com/vue@2.5.21"></script> <div id="app"> <user-list></user-list> //登録したuser-listコンポーネントをタグのようにして呼び出すことができる。 </div>コンポーネントの入れ子構造
Vueインスタンスをルートとして、その下にコンポーネントがつながっている。
コンポーネント内のtemplateで他のコンポーネントを呼び出すこともできる。
※コンポーネントのtemplate直下には一つの要素しか書けないことに注意。以下では、use-listコンポーネントのtemplateで<list-title></list-title>を呼び出してみる。
jsVue.component('list-title', { template:` <h2>ユーザーリスト</h2> ` }) Vue.component('user-list', { data(){ return { users: [ {id: 1, name: '1ちゃん'}, {id: 2, name: '2ちゃん'}, {id: 3, name: '3ちゃん'}, {id: 4, name: '4ちゃん'}, {id: 5, name: '5ちゃん'} ] } }, template:` <div> <list-title></list-title> <ul> <li v-for="user in users" :key="user.id"> {{ user.name }} </li> </ul> </div> ` }) const vm = new Vue({ el: '#app', })出力結果
ユーザーリスト
- 1ちゃん
- 2ちゃん
- 3ちゃん
- 4ちゃん
- 5ちゃん
ローカル登録とグローバル登録
・グローバル登録
→Vueのtemplate上のどこからでも呼び出して使うことができるが、使用していない場合もコンポーネントの読み込みが発生してしまう。・ローカル登録
→コンポーネント名:コンポーネントのオブジェクト の形式で登録する。Vueインスタンスのcomponentsオプション配下のみで使用する。ここまでグローバル登録してきたcomponentを、ローカル登録に書き換えてみる。
jsconst ListTitle = { //オブジェクトの形式に変える template:` <h2>ユーザーリスト</h2> ` } Vue.component('user-list', { components: { 'list-title': ListTitle //user-listコンポーネントのcomponents内でローカル登録する },ローカル登録の場合は登録したVueインスタンスのcomponentsオプション配下のみで使用できるので、以下のようにマウント先で呼び出しても機能しない。
html<div id="app"> <list-title></list-title> <user-list></user-list> </div>親コンポーネントから子コンポーネントへデータを渡す
コンポーネント内に定義したdataは、そのままでは他のコンポーネントから参照したり書き換えることができない。
そこでpropsを使えば、親から子への単一方向のデータの受け渡しが可能になる。例として、「親コンポーネント:UserList」から「子コンポーネント:UserDetail」へデータを受け渡してみる。
・子コンポーネントを作成
jsconst UserDetail = { props: { user: { type: Object } //Object型のuserというプロパティを親コンポーネントから受け取る //親コンポーネントでUserDetailコンポーネントを呼び出す際、 //v-bind:user=<ユーザーオブジェクト>の形で値を渡すことを想定している }, template:` <div> <h2>選択中のユーザー</h2> {{ user.name }} //propsもdataと同様にアクセスできる </div> ` }・親コンポーネントを編集
jsconst UserList = { components: { 'list-title': ListTitle, 'user-detail': UserDetail //上で作成したUserDetailをローカル登録する }, data(){ return { users: [ {id: 1, name: '1ちゃん'}, {id: 2, name: '2ちゃん'}, {id: 3, name: '3ちゃん'}, {id: 4, name: '4ちゃん'}, {id: 5, name: '5ちゃん'} ], selected_user: {} //クリックイベントで選択中のユーザーを受け取るdataを定義し、 //デフォルトでは空のオブジェクトを入れておく } }, template:` <div> <list-title></list-title> <ul> <li v-for="user in users" :key="user.id" @click='selected_user = user'> //selected_userにクリックされたユーザーを格納する {{ user.name }} </li> </ul> <user-detail :user='selected_user'></user-detail> //クリックされたユーザーをv-bindでuser-detailへ受け渡す </div> ` }このように、親コンポーネントのv-bindで渡したデータが、子コンポーネントのpropsに渡る。
propsの注意点
・v-bindの名前とpropsの名前は一致する必要がある。
・命名の規則
v-bind:ケバブケースで記述(user-name)
props:キャメルケースで記述(userName)・propsで受け渡されたオブジェクト自体は直接書き換えることができないが、オブジェクト内のプロパティを書き換えることはできる。
例:propsにuserというオブジェクとが定義されている場合。
→user.name = '名前' のようにuserのプロパティへの代入はエラーにならないが、
user = {} のようにuser自体への代入はエラーになる。子コンポーネントから親コンポーネントへデータを渡す
例として、子コンポーネントでユーザー名を編集・登録し、親コンポーネントでそのユーザー名を表示するフォームを作ってみる。
・親となるVueインスタンス
jsconst vm = new Vue({ el: '#app', components: { 'user-detail': UserDetail } })・HTML上で、Vueインスタンスにローカル登録されたUserDetailコンポーネントを表示する。
html<!DOCTYPE html> <script src="https://unpkg.com/vue@2.5.21"></script> <div id="app"> <user-detail></user-detail> </div>・親コンポーネント:UserDetail
user_nameというdataを持ち、templateで呼び出している。
またローカル登録したuser-formコンポーネントをtemplateで呼び出し、user_nameをv-bindで渡している。jsconst UserDetail = { components: { 'user-form' : UserForm //このあと定義する子コンポーネントUserFormをローカル登録する }, data(){ return { user_name: 'サトウ ハナコ' } }, template:` <div> <div> <span>ユーザー名: {{ user_name }} </span> </div> <div> <user-form :user-name='user_name' @update:user-name='user_name = $event'></user-form> //user-formコンポーネントには、v-bindでuser_nameを渡す //このあと定義するupdateメソッド </div> </div> ` }・子コンポーネント:UserForm
jsconst UserForm = { template:` <div> <div>ユーザー名変更フォーム</div> <input v-model='user_name' /> <button @click='update'>名前変更</button> </div> `, //user_nameを編集するinputタグと、確定するボタンを設置 props:{ userName: { type: String, required: true } }, data(){ return { user_name: this.userName } //user_nameを編集するため、propsをdataに設定する }, methods: { update(){ this.$emit('update:user-name', this.user_name) } //templateで設置したボタンがクリックされると、updateメソッドが呼び出される //$emitメソッドで親にデータを渡す } }$emitメソッド:親コンポーネントへデータを渡す際に使う。
・第一引数…v-onのイベント名(ここでは親コンポーネントで設定した@update:user-nameのイベントハンドラが実行される)
・第二引数…親コンポーネントに渡す値ここで親コンポーネント「UserDatail」を見てみる。
<user-form :user-name='user_name' @update:user-name='user_name = $event'> </user-form>この$eventには、$emitの第二引数にしていしたthis.user_nameが格納される。
これで親コンポーネントによりユーザー名が表示され、子コンポーネントによりユーザー名変更フォームが表示される。
フォームにテキストを入力してボタンをクリックすると、親コンポーネントによるユーザー名の表示も変更されるようになる。sync修飾子を使ってデータを渡す
ここまでで書いたコードを、sync修飾子を使って書き換えてみる。
sync修飾子は親コンポーネントのv-bind属性に指定する。
・親コンポーネント「UserDatail」のv-bindが使われている部分を編集する。
sync修飾子をつけることで、@以下を記述する必要がなくなる。変更前 <user-form :user-name='user_name' @update:user-name='user_name = $event'> </user-form> 変更後 <user-form :user-name.sync='user_name'> </user-form>・子コンポーネント「UserForm」の$emitの呼び出し部分を編集する。
$emitの第一引数を'メソッド名: propsの名前'とする。変更前 methods: { update(){ this.$emit('update:user-name', this.user_name) } } 変更後 methods: { update(){ this.$emit('update:userName', this.user_name) } }これでコードを実行すると、同じように動作する。
スロットの使い方
slotとは親となるコンポーネント側から、子のコンポーネントのテンプレートの一部を差し込む機能。
以下の例で、使い方を見てみる。
・js
header・main・footerのそれぞれにslotタグが使われており、layoutコンポーネントを使用する側から、この3つのslotにコンテンツを挿入することができる。jsconst Layout = { template:` <div class ="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> ` } const vm = new Vue({ el: '#app', components: { 'layout': Layout } })・html
layoutタグでlayoutコンポーネントを呼び出し、slotに挿入するコンテンツを記述する。html<!DOCTYPE html> <script src="https://unpkg.com/vue@2.5.21"></script> <div id="app"> <layout> <template slot='header'> //name="header"と指定されているslotタグの位置に挿入される Header </template> Main コンテンツ //slot属性を指定せずに記述した場合、 layoutコンポーネントの名前なしslotの位置に挿入される <span slot='footer'> //spanのような通常のhtml要素にslot属性を指定した場合は、 span要素自体がslotの位置に挿入される Footer </span> </layout> </div>
- 投稿日:2021-03-21T11:53:24+09:00
【Vue.js】コンポーネント ~作成からデータの受け渡しまで~
コンポーネントとは
・画面を構成する要素を個々のUIパーツに分割したもの。
・Vue.jsでは個々のコンポーネントがVueインスタンスとなっており、コンポーネントの組み合わせによって画面を作成していく。
・メリット →複数の画面で再生できる。コードの見通しがよくなる。
コンポーネントの作成手順
ここでは一つのjavascriptファイルで記述していく。
・componentを登録する
componentメソッドを使う。
第一引数には任意のコンポーネント名を指定する。
第二引数にはvueインスタンスを生成する際のオプションと同様のものが指定できる。
※vueインスタンス生成時との違い…dataでは必ず関数の戻り値でオブジェクトを指定する。
※コンポーネントの登録は、インスタンスの生成よりも前に記述する。jsVue.component('user-list', { //componentメソッドでcomponentを登録する。 data(){ //dataでは常に関数の戻り値でオブジェクトを設定する。 return { users: [ {id: 1, name: '1ちゃん'}, {id: 2, name: '2ちゃん'}, {id: 3, name: '3ちゃん'}, {id: 4, name: '4ちゃん'}, {id: 5, name: '5ちゃん'} ] } }, template:` <ul> <li v-for="user in users" :key="user.id"> {{ user.name }} </li> </ul> ` }) const vm = new Vue({ el: '#app', })html<script src="https://unpkg.com/vue@2.5.21"></script> <div id="app"> <user-list></user-list> //登録したuser-listコンポーネントをタグのようにして呼び出すことができる。 </div>コンポーネントの入れ子構造
Vueインスタンスをルートとして、その下にコンポーネントがつながっている。
コンポーネント内のtemplateで他のコンポーネントを呼び出すこともできる。
※コンポーネントのtemplate直下には一つの要素しか書けないことに注意。以下では、use-listコンポーネントのtemplateで<list-title></list-title>を呼び出してみる。
jsVue.component('list-title', { template:` <h2>ユーザーリスト</h2> ` }) Vue.component('user-list', { data(){ return { users: [ {id: 1, name: '1ちゃん'}, {id: 2, name: '2ちゃん'}, {id: 3, name: '3ちゃん'}, {id: 4, name: '4ちゃん'}, {id: 5, name: '5ちゃん'} ] } }, template:` <div> <list-title></list-title> <ul> <li v-for="user in users" :key="user.id"> {{ user.name }} </li> </ul> </div> ` }) const vm = new Vue({ el: '#app', })出力結果
ユーザーリスト
- 1ちゃん
- 2ちゃん
- 3ちゃん
- 4ちゃん
- 5ちゃん
ローカル登録とグローバル登録
・グローバル登録
→Vueのtemplate上のどこからでも呼び出して使うことができるが、使用していない場合もコンポーネントの読み込みが発生してしまう。・ローカル登録
→コンポーネント名:コンポーネントのオブジェクト の形式で登録する。Vueインスタンスのcomponentsオプション配下のみで使用する。ここまでグローバル登録してきたcomponentを、ローカル登録に書き換えてみる。
jsconst ListTitle = { //オブジェクトの形式に変える template:` <h2>ユーザーリスト</h2> ` } Vue.component('user-list', { components: { 'list-title': ListTitle //user-listコンポーネントのcomponents内でローカル登録する },ローカル登録の場合は登録したVueインスタンスのcomponentsオプション配下のみで使用できるので、以下のようにマウント先で呼び出しても機能しない。
html<div id="app"> <list-title></list-title> <user-list></user-list> </div>親コンポーネントから子コンポーネントへデータを渡す
コンポーネント内に定義したdataは、そのままでは他のコンポーネントから参照したり書き換えることができない。
そこでpropsを使えば、親から子への単一方向のデータの受け渡しが可能になる。例として、「親コンポーネント:UserList」から「子コンポーネント:UserDetail」へデータを受け渡してみる。
・子コンポーネントを作成
jsconst UserDetail = { props: { user: { type: Object } //Object型のuserというプロパティを親コンポーネントから受け取る //親コンポーネントでUserDetailコンポーネントを呼び出す際、 //v-bind:user=<ユーザーオブジェクト>の形で値を渡すことを想定している }, template:` <div> <h2>選択中のユーザー</h2> {{ user.name }} //propsもdataと同様にアクセスできる </div> ` }・親コンポーネントを編集
jsconst UserList = { components: { 'list-title': ListTitle, 'user-detail': UserDetail //上で作成したUserDetailをローカル登録する }, data(){ return { users: [ {id: 1, name: '1ちゃん'}, {id: 2, name: '2ちゃん'}, {id: 3, name: '3ちゃん'}, {id: 4, name: '4ちゃん'}, {id: 5, name: '5ちゃん'} ], selected_user: {} //クリックイベントで選択中のユーザーを受け取るdataを定義し、 //デフォルトでは空のオブジェクトを入れておく } }, template:` <div> <list-title></list-title> <ul> <li v-for="user in users" :key="user.id" @click='selected_user = user'> //selected_userにクリックされたユーザーを格納する {{ user.name }} </li> </ul> <user-detail :user='selected_user'></user-detail> //クリックされたユーザーをv-bindでuser-detailへ受け渡す </div> ` }このように、親コンポーネントのv-bindで渡したデータが、子コンポーネントのpropsに渡る。
propsの注意点
・v-bindの名前とpropsの名前は一致する必要がある。
・命名の規則
v-bind:ケバブケースで記述(user-name)
props:キャメルケースで記述(userName)・propsで受け渡されたオブジェクト自体は直接書き換えることができないが、オブジェクト内のプロパティを書き換えることはできる。
例:propsにuserというオブジェクとが定義されている場合。
→user.name = '名前' のようにuserのプロパティへの代入はエラーにならないが、
user = {} のようにuser自体への代入はエラーになる。子コンポーネントから親コンポーネントへデータを渡す
例として、子コンポーネントでユーザー名を編集・登録し、親コンポーネントでそのユーザー名を表示するフォームを作ってみる。
・親となるVueインスタンス
jsconst vm = new Vue({ el: '#app', components: { 'user-detail': UserDetail } })・HTML上で、Vueインスタンスにローカル登録されたUserDetailコンポーネントを表示する。
html<!DOCTYPE html> <script src="https://unpkg.com/vue@2.5.21"></script> <div id="app"> <user-detail></user-detail> </div>・親コンポーネント:UserDetail
user_nameというdataを持ち、templateで呼び出している。
またローカル登録したuser-formコンポーネントをtemplateで呼び出し、user_nameをv-bindで渡している。jsconst UserDetail = { components: { 'user-form' : UserForm //このあと定義する子コンポーネントUserFormをローカル登録する }, data(){ return { user_name: 'サトウ ハナコ' } }, template:` <div> <div> <span>ユーザー名: {{ user_name }} </span> </div> <div> <user-form :user-name='user_name' @update:user-name='user_name = $event'></user-form> //user-formコンポーネントには、v-bindでuser_nameを渡す //このあと定義するupdateメソッド </div> </div> ` }・子コンポーネント:UserForm
jsconst UserForm = { template:` <div> <div>ユーザー名変更フォーム</div> <input v-model='user_name' /> <button @click='update'>名前変更</button> </div> `, //user_nameを編集するinputタグと、確定するボタンを設置 props:{ userName: { type: String, required: true } }, data(){ return { user_name: this.userName } //user_nameを編集するため、propsをdataに設定する }, methods: { update(){ this.$emit('update:user-name', this.user_name) } //templateで設置したボタンがクリックされると、updateメソッドが呼び出される //$emitメソッドで親にデータを渡す } }$emitメソッド:親コンポーネントへデータを渡す際に使う。
・第一引数…v-onのイベント名(ここでは親コンポーネントで設定した@update:user-nameのイベントハンドラが実行される)
・第二引数…親コンポーネントに渡す値ここで親コンポーネント「UserDatail」を見てみる。
<user-form :user-name='user_name' @update:user-name='user_name = $event'> </user-form>この$eventには、$emitの第二引数にしていしたthis.user_nameが格納される。
これで親コンポーネントによりユーザー名が表示され、子コンポーネントによりユーザー名変更フォームが表示される。
フォームにテキストを入力してボタンをクリックすると、親コンポーネントによるユーザー名の表示も変更されるようになる。sync修飾子を使ってデータを渡す
ここまでで書いたコードを、sync修飾子を使って書き換えてみる。
sync修飾子は親コンポーネントのv-bind属性に指定する。
・親コンポーネント「UserDatail」のv-bindが使われている部分を編集する。
sync修飾子をつけることで、@以下を記述する必要がなくなる。変更前 <user-form :user-name='user_name' @update:user-name='user_name = $event'> </user-form> 変更後 <user-form :user-name.sync='user_name'> </user-form>・子コンポーネント「UserForm」の$emitの呼び出し部分を編集する。
$emitの第一引数を'メソッド名: propsの名前'とする。変更前 methods: { update(){ this.$emit('update:user-name', this.user_name) } } 変更後 methods: { update(){ this.$emit('update:userName', this.user_name) } }これでコードを実行すると、同じように動作する。
スロットの使い方
slotとは親となるコンポーネント側から、子のコンポーネントのテンプレートの一部を差し込む機能。
以下の例で、使い方を見てみる。
・js
header・main・footerのそれぞれにslotタグが使われており、layoutコンポーネントを使用する側から、この3つのslotにコンテンツを挿入することができる。jsconst Layout = { template:` <div class ="container"> <header> <slot name="header"></slot> </header> <main> <slot></slot> </main> <footer> <slot name="footer"></slot> </footer> </div> ` } const vm = new Vue({ el: '#app', components: { 'layout': Layout } })・html
layoutタグでlayoutコンポーネントを呼び出し、slotに挿入するコンテンツを記述する。html<!DOCTYPE html> <script src="https://unpkg.com/vue@2.5.21"></script> <div id="app"> <layout> <template slot='header'> //name="header"と指定されているslotタグの位置に挿入される Header </template> Main コンテンツ //slot属性を指定せずに記述した場合、 layoutコンポーネントの名前なしslotの位置に挿入される <span slot='footer'> //spanのような通常のhtml要素にslot属性を指定した場合は、 span要素自体がslotの位置に挿入される Footer </span> </layout> </div>