20210321のvue.jsに関する記事は11件です。

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メリットデメリットがあればコメントしていただけますと幸いです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsにおけるコンポーネントの基本

Vue.jsのコンポーネントの基本

Vue.jsにおけるコンポーネントについて解説していきます。

コンポーネントとは

コンポーネントとはWebサイトを構成する部品を分割し、部品ごとにソースコード(HTML、CSS、JavaScriptなど)にまとめ一つのモジュールにすることをいいます。
大抵のWebサイトには検索フォーム、ナビゲーション、ヘッダー、フッター、サイドバーに分かれていますが、ページ毎に共通のソースコードがあり、同じ内容を、毎回記載していくのはあまり効率が良くありません。
そこで、各パーツ毎にソースコードをまとめて共通化する事で、効率よく開発をすすめることができます。
Vue.jsでは、このコンポーネントという機能を使い、パーツを共通化しています。

コンポーネントの作成方法


コンポーネントを作成する為には、Vue.component()メゾットを使い、コンポーネントを登録する必要があります。
Vue.component()メゾットを使用することで、グローバルスコープにコンポーネントを登録することになり、以後どこからでもコンポーネントにアクセスすることができます。

main.js
Vue.component('コンポーネント名',{
    template : HTMLの内容,
})


コンポーネント名にはコンポーネントとして使用したい名前を自由に決めることがでます。
template以降は表示したい内容をHTMLを利用して記載します。
例えば、hello-world.js内のtemplateにHello-Worldと記述したとしましょう。

main.js
Vue.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.js
var 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.js
Vue.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.js
Vue.component('hello-world',{
    template: <div><p>Hello-World</p><p>I'm Masaki</p></div>
})



templateタグはテンプレート全体を ` で囲むと改行することができます。

main.js
Vue.component('hello-world',{
    template: `
            <div>
              <p>Hello-World</p><p>I'm Masaki</p>
            </div>`
})

ローカルスコープの登録

コンポーネントの登録にはグローバルスコープとローカルスコープの2つの方法があります。
Vue.component()メゾットを使用する場合はグローバルコンポーネントとなり、どこからでもコンポーネントを利用することができます。
一方でコンポーネントをローカルに登録することで、ある特定のコンポーネント以外は、参照することができないように制限をかけることができます。
ローカルへの登録は、親コンポーネントのcomponentオプションに子コンポーネントを定義することでローカルスコープとなります。

子コンポーネントにコンポーネントを定義します。

main.js
var 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.js
Vue.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.js
Vue.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.js
var app = new Vue ({
     el:'#app',
     data: {
       price: 1000
  },
   methods:{
      priceDown: function(){
        this.price -=100;
    }
  }
})

以上です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BuefyからPrimeVueに移行

vueが3.0になり、そろそろ移行しようかなーと思ってやってみると、普段使っていたBuefyが3.0に未対応のようです。
開発者の方々?はPrimeVueで忙しい的なことを書いていたので移行を検討。
vue3.0に対応しているし、Buefyで使っていたコントロールはほぼ網羅している。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

シンプルで使いやすいJSONフォーマットツールを作ってみた

はじめに

こんにちは、システム開発チーム「presto」です。
開発業務(プログラミング中)に「こんなツール、あんなツールが身近なところにあったらいいなぁ」と思ったことはありませんか?

ここではそんなツールをVuetifyを使って作ってみたので紹介させてください。

JSONフォーマットツール

今回紹介させていただくのは、JSONフォーマットツールです。

画面は以下の通りです。

JSONフォーマットツール

使い方

使い方は以下の通りです。
1. 画面上部のテキストエリアにJSON文字列を入力する
2. (任意)フォーマットのインデント数を指定する
3. 結果確認

1. 画面上部のテキストエリアにJSON文字列を入力する

JSONフォーマットツール_手順1

例として、以下を入力します。

{"number":12345,"result":true}

JSONフォーマットツール_手順1_入力内容

2. (任意)フォーマットのインデント数を指定する

デフォルトのインデント数は「4」となっています。

JSONフォーマットツール_手順2_インデント数指定

3. 結果確認

以下のような値が表示されます。

{
    "number": 12345,
    "result": true
}

JSONフォーマットツール_手順3_フォーマット結果

インデント数を「0」と指定すると、整形後のJSON文字列がMinifyされます。

{"number":12345,"result":true}

JSONフォーマットツール_手順3_インデント数指定_Minify

JSONフォーマットできない文字列を入力した場合

JSONフォーマットできない文字列を入力した場合、SyntaxErrorが表示されます。

SyntaxError: Unexpected token d in JSON at position 6

JSONフォーマットツール_SyntaxError

まとめ

今回は、JSONフォーマットツールの紹介をさせていただきました。

今後もよろしくお願いします。
ありがとうございましたー!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Docker で Vue.js + Spring REST をやってみる

001.png

想定する対象

  • 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 部分は省略

目次

  1. Docker を使ってみる
  2. HTML ファイルを表示させる
  3. 配布可能な Docker アプリを作成する(※簡易版)
  4. Vue.js を導入する
  5. Ajax リクエスト処理の追加
  6. バックエンドサーバーの構築
  7. Java WEBアプリの作成
  8. 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     NAMES

Docker イメージの削除

  • 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.ymlvolumes: の項目でローカル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.ymljettyvolumes 設定でコンテナに配置します
    • ここでは ./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.java

xml 設定ファイルの作成

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 の標準仕様の一部分です

    • JettyTomcat は Servlet API の標準仕様を実装した Java の WEBサーバーです
      • JettyTomcat は 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 とは関係ない独自の Servletweb.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-packageorg.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 コンテナとなります

リポジトリ

minimum-docker-vue-spring


ありがとうございました

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.jsでフロント側でフォームにバリデーションをつけよう!

概要

Nuxt.jsを使用しformにフロント側でバリデーションをつけていきます。

前提知識

html, css, Vue.jsの基本的な知識

プロジェクトの作成

今回はデスクトップに作成していきます

cd ~/Desctop
npx create-nuxt-app validate
npm run dev

スクリーンショット 2021-03-21 11.22.19.png

こちらが立ち上げれば環境構築は成功です。

 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-21 17.43.34.png

スクリーンショット 2021-03-21 17.43.54.png

お疲れ様でした!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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がなければログイン画面にリダイレクトさせるというものです.
詳しい説明は省きますがtofromはいずれもRouteLocationNormalizedというオブジェクトです. そのオブジェクトのメンバーの1つとしてqueryがあるので、そこから取得をします.

router.beforeEach((to, from, next) => {
  // toからqueryを取得できます
  const auth = to.query.auth
  if (!auth) {
    next({ path: '/login', query: { redirect: to.fullPath } })
  }
})

参考

あとがき

Vue3.0系はまだまだ参考になる記事が少ないですね。
Vueエンジニアの方々一緒に頑張りましょう〜

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【vue.js】Font awesomeのアイコンを並べる【データバインディング】

環境

Vue.js 6.14.9
Vue CLI 4.5.9

Font 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>

あれ、エラーが、、、
スクリーンショット 2021-03-21 15.17.13.png
上記のエラーをみると、
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-21 14.51.04.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【エラー備忘録】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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】コンポーネント

コンポーネントとは

・画面を構成する要素を個々のUIパーツに分割したもの。

・Vue.jsでは個々のコンポーネントがVueインスタンスとなっており、コンポーネントの組み合わせによって画面を作成していく。

・メリット →複数の画面で再生できる。コードの見通しがよくなる。

コンポーネントの作成手順

ここでは一つのjavascriptファイルで記述していく。

・componentを登録する

componentメソッドを使う。
第一引数には任意のコンポーネント名を指定する。
第二引数にはvueインスタンスを生成する際のオプションと同様のものが指定できる。
 ※vueインスタンス生成時との違い…dataでは必ず関数の戻り値でオブジェクトを指定する。
 ※コンポーネントの登録は、インスタンスの生成よりも前に記述する。

js
Vue.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>を呼び出してみる。

js
Vue.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を、ローカル登録に書き換えてみる。

js
const 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」へデータを受け渡してみる。

・子コンポーネントを作成

js
const UserDetail = {
  props: {
    user: {
      type: Object
    }
        //Object型のuserというプロパティを親コンポーネントから受け取る
     //親コンポーネントでUserDetailコンポーネントを呼び出す際、
        //v-bind:user=<ユーザーオブジェクト>の形で値を渡すことを想定している
  },
  template:`
    <div>
      <h2>選択中のユーザー</h2>
      {{ user.name }}
      //propsもdataと同様にアクセスできる
    </div>
  `
}

・親コンポーネントを編集

js
const 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インスタンス

js
const 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で渡している。

js
const 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

js
const 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にコンテンツを挿入することができる。

js
const 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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue.js】コンポーネント ~作成からデータの受け渡しまで~

コンポーネントとは

・画面を構成する要素を個々のUIパーツに分割したもの。

・Vue.jsでは個々のコンポーネントがVueインスタンスとなっており、コンポーネントの組み合わせによって画面を作成していく。

・メリット →複数の画面で再生できる。コードの見通しがよくなる。

コンポーネントの作成手順

ここでは一つのjavascriptファイルで記述していく。

・componentを登録する

componentメソッドを使う。
第一引数には任意のコンポーネント名を指定する。
第二引数にはvueインスタンスを生成する際のオプションと同様のものが指定できる。
 ※vueインスタンス生成時との違い…dataでは必ず関数の戻り値でオブジェクトを指定する。
 ※コンポーネントの登録は、インスタンスの生成よりも前に記述する。

js
Vue.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>を呼び出してみる。

js
Vue.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を、ローカル登録に書き換えてみる。

js
const 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」へデータを受け渡してみる。

・子コンポーネントを作成

js
const UserDetail = {
  props: {
    user: {
      type: Object
    }
        //Object型のuserというプロパティを親コンポーネントから受け取る
     //親コンポーネントでUserDetailコンポーネントを呼び出す際、
        //v-bind:user=<ユーザーオブジェクト>の形で値を渡すことを想定している
  },
  template:`
    <div>
      <h2>選択中のユーザー</h2>
      {{ user.name }}
      //propsもdataと同様にアクセスできる
    </div>
  `
}

・親コンポーネントを編集

js
const 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インスタンス

js
const 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で渡している。

js
const 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

js
const 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にコンテンツを挿入することができる。

js
const 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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む