- 投稿日:2020-11-17T22:51:16+09:00
VueプロジェクトにVuetify追加
すでにVueプロジェクトがある状態から始める
Vueプロジェクト作成は こちら を参照
Vueは3系を使用コンテナに入る
alpine linuxを使っているのでashです
⛄ docker exec -it <container-name> ashvuetify 追加
vue add vuetifyエラー出現...
? Invoking generator for vue-cli-plugin-vuetify... ERROR Error: You cannot call "get" on a collection with no paths. Instead, check the "length" property first to verify at least 1 path exists.こちらによると、vue3系はbetaなのでveu2系を使って欲しいとのことでした。
最新にしすぎた...結局vue2系にダウングレードして再チャレンジ
3系のアプリはいったん削除して2系のプロジェクトを作成
(削除しないでいい方法もあると思いますが、今回は全部吹き飛ばしました)再チャレンジ
vue add vuetify成功したようなのでブラウザから確認
Portなどは環境に応じて適宜変更してくださいhttp://localhost:8080/
- 投稿日:2020-11-17T22:26:07+09:00
5分で作るDocker+Laravel PHP+Vue.js開発環境構築
開発環境 macOS Big Sur 下記の記事を参考に環境構築を行ったのですが、
やれwget
コマンドが無いだの、apt-get
コマンドが無いだの初心者の方への導入への障壁がいくつかあったため、
それを乗り越えるための記事にしたいと思います。(※注:下記の記事は決して悪くありません。)
タイトル リンク 5分で作るLaravel+Vue.js開発環境(docker-compose) https://qiita.com/yusukeito58/items/37bd551560e495dbd1b8
wget
コマンドインストール
タイトル リンク Mac - wgetコマンドをインストール(使えるようにする) https://qiita.com/th4inf/items/f85c1b91065d85af67b9
←apt-getコマンドが無いかと思い調べてが結果的にいらないのでやらんで良いapt-get
コマンドインストール
タイトル リンク install apt-get to Mac https://qiita.com/th4inf/items/f85c1b91065d85af67b9
docker-compose up -d --build
時にmysql-client
のエラーがでたらエラー内容
E: Package 'mysql-client' has no installation candidate ERROR: Service 'app' failed to build : The command '/bin/sh -c apt-get update && apt-get install -y zlib1g-dev mysql-client libpng-dev libjpeg-dev gnupg curl wget && docker-php-ext-configure gd --with-png-dir=/usr/include --with-jpeg-dir=/usr/include && docker-php-ext-install zip pdo_mysql gd' returned a non-zero code: 100
タイトル リンク docker-compose buildするときにbundle installやmysql-clientでコケた話 https://qiita.com/aseanchild1400/items/d3580366054fee3d2703
- 投稿日:2020-11-17T21:53:02+09:00
自作PC特化だけど、リモート起動や自動起動を好きにカスタムできるWebアプリをobnizで試作してみた
自作PCの自動起動を好きにカスタムできる良い感じのアプリが無いので、自分で製作を考えている。試しにobnizを利用してWebアプリから電源ONを試作。 ※今回は電源ONの代わりにLED点灯を試す。
ちょっと分かり辛いが、電源ONクリックするとLEDが1秒点灯して消える。
Webアプリ画面
曜日別に起動時間を設定したいので、曜日別時間設定画面を設けた。データベース保存はまだ実装出来ていない。CodePenはコチラから
コード
.html<!DOCTYPE html> <html lang="jp" > <head> <meta charset="UTF-8"> <title>HPC-accede</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/css/bootstrap.min.css'> </head> <body> <!-- 全体をVue.js有効にする --> <div id="app" class="container text-white bg-dark p-1"> <!-- タイトル --> <div class="row text-center"> <div class="col-sm-6 mx-auto"><h1>HPC-accede</h1></div> </div> <!-- 設定 --> <div class="form-group my-3 mx-4"> <div class="border-bottom col-sm-12"><h5>Obniz ID</h5></div> </div> <div class="form-group form-inline my-3 mx-5"> <input v-model:value="ObnizID[0]" class="form-control" type="text" maxlength="4" style="width:80px;"> <label class="control-label mx-2">-</label> <input v-model:value="ObnizID[1]" class="form-control" type="text" maxlength="4" style="width:80px;"> </div> <!-- 即時電源ON --> <div class="form-group my-3 mx-4"> <div class="border-bottom col-sm-12"><h5>即時起動</h5></div> </div> <div class="form-group my-3 mx-5"> <button v-on:click="PowerON" class="btn btn-success">電源ON</button> </div> <!-- 曜日指定 --> <div class="form-group my-3 mx-4"> <div class="border-bottom col-sm-12"><h5>曜日別時間設定</h5></div> </div> <div class="form-group form-inline col-sm-12"> <!-- 日曜 --> <div class="form-inline col-sm-4 mb-2"> <label class="control-label mx-3">日曜:</label> <input v-model:value="WeekTime['Sun']" class="form-control" type="time" step="1" style="width:130px;"> </div> <!-- 月曜 --> <div class="form-inline col-sm-4 mb-2"> <label class="control-label mx-3">月曜:</label> <input v-model:value="WeekTime['Mon']" class="form-control" type="time" step="1" style="width:130px;"> </div> <!-- 火曜 --> <div class="form-inline col-sm-4 mb-2"> <label class="control-label mx-3">火曜:</label> <input v-model:value="WeekTime['Tue']" class="form-control" type="time" step="1" style="width:130px;"> </div> <!-- 水曜 --> <div class="form-inline col-sm-4 mb-2"> <label class="control-label mx-3">水曜:</label> <input v-model:value="WeekTime['Wed']" class="form-control" type="time" step="1" style="width:130px;"> </div> <!-- 木曜 --> <div class="form-inline col-sm-4 mb-2"> <label class="control-label mx-3">木曜:</label> <input v-model:value="WeekTime['Thu']" class="form-control" type="time" step="1" style="width:130px;"> </div> <!-- 金曜 --> <div class="form-inline col-sm-4 mb-2"> <label class="control-label mx-3">金曜:</label> <input v-model:value="WeekTime['Fri']" class="form-control" type="time" step="1" style="width:130px;"> </div> <!-- 土曜 --> <div class="form-inline col-sm-4 mb-2"> <label class="control-label mx-3">土曜:</label> <input v-model:value="WeekTime['Sat']" class="form-control" type="time" step="1" style="width:130px;"> </div> </div> </div> <!-- CDN --> <script src='https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.3/js/bootstrap.min.js'></script> <script src='https://unpkg.com/obniz@3.9.0/obniz.js'> </script><script src="./script.js"></script> </body> </html>script.js// 任意の秒数待つことができる関数 const sleep = (msec) => new Promise(res => setTimeout(res, msec)); // Obniz関数 let obniz; // Obniz呼び出し関数 const connect = function(func, ob){ console.log(ob.connectionState); // Obnizへの接続を確認 if (ob.connectionState === "connected") { func(); } else { ob.on('connect', () => { func(); }) } } const app = new Vue({ el: '#app', // Vueが管理する一番外側のDOM要素 data: { // Vue内部で利用する変数定義 ObnizID: ['0000', '0000'], WeekTime: [ {'Sun':'00:00:00'}, {'Mon':'00:00:00'}, {'Tue':'00:00:00'}, {'Wed':'00:00:00'}, {'Thu':'00:00:00'}, {'Fri':'00:00:00'}, {'Sat':'00:00:00'}, ], }, methods: { // 関数はココに記述 PowerON: function() { // LED ON // Obniz ID 指定 let obnizid = `${this.ObnizID[0]}-${this.ObnizID[1]}`; console.log(obnizid); this.obniz = new Obniz(obnizid); let me = this; // thisを関数内で使えないので変数に代入 // connect関数を呼んで、connect関数内で以下のFunctionを実行 connect(async function() { const led = me.obniz.wired('LED', { anode: 0, cathode: 1 }); me.obniz.display.clear(); me.obniz.display.print('ON'); led.on(); await sleep(1000); led.off(); // LED点灯 1s me.obniz.display.clear(); }, this.obniz); }, }, });弟に使い勝手とかを聞いてみた
今回はLED点灯だが、完成後はPCが起動するようになるイメージを持ってもらったうえで使ってもらった。
操作面
- 初回実行時のLED点灯の動作がちょっとおかしい。 ※コーディングミスってるかも。
- 初期設定(ObnizID)は別メニューの方が良いと思う。
- 電源ON を押したらすぐ起動しちゃうの?間違えて起動とか考慮したら?
- 毎日を同じ時間にしたい場合、一つずつ設定するのは面倒では?
- 電源OFF は出来ないんだよね?(聞いてみただけみたい。)
- この日だけ臨時で時間設定とか出来たら良いのでは?
- プリセット登録(パターンを予め設定できる)とかどう?
企画面
- 自作PCじゃないから使わないな。(自作PCでしか出来ない?)
- 同じ仕組みで遠隔起動出来ると良いかもしれないけど、思いつかないね。(私も思いつかない。)
なぜ自動起動したいか(補足)
PCでTV録画しているからって理由。毎日同じ時間で起動はBIOSで設定できるけど、もう少し起動時間をフレキシブルに変更したいっていう私のわがまま。
想定イメージ
今回はWebアプリとObnizの連携部分のみの実装。LED部分をリレーと置き換え、自作PCのPowerSWと置き換えてショートさせれば電源ON出来る想定。(たぶん。)
- 投稿日:2020-11-17T19:48:10+09:00
Vue.js+BootstrapでAPIを叩いてその結果をCardで表示する
はじめに
かれこれ3日ぐらい詰まってた問題が解決したので軽くまとめておこうと思います。データベースを検索するAPIを使ってその結果をCardで表示する感じです。
完成図
つかうもの
・Vue.js
・Bootstrap
・axiosやってみよう
まずは検索フォーム自体の設計です。これは横一列にするのに苦戦しただけでサクッとできました。
song.vue<div> <div class="container"> <form class="form-inline mx-auto" style="width: 500px;"> <div class="form-group"> <select class="form-control" v-model="type"> <option>曲名から</option> <option>アーティスト名から(未実装)</option> </select> <input class="form-control" type="search" placeholder="Search" aria-label="Search" v-model="keyword"> <button class="btn btn-outline-success" type="submit" v-on:click="Search()">Search</button> </div> </form> <div><br>検索は完全一致です<br></div> </div> </div>将来的な事を考えてSelectという項目も用意しておりますが今は意味ないです。v-modelで変数を指定して、v-on:clickで検索プログラム自体を呼び出しましょう。
次はそれを表示する部分です。
song.vue<div> <div class="container"> <div class="row"> <div class="card border-info col-sm-4" v-for="item in items" :key="item.Artist"> <div class="card-body"> <h2 class="card-title">{{ item.Title }}</h2> <ul class="list-group list-group-flush"> <li class="list-group-item">アーティスト名:{{ item.Artist }}</li> <li class="list-group-item">作詞:{{ item.Word }}</li> <li class="list-group-item">作曲:{{ item.Composer }}</li> <li class="list-group-item">編曲:{{ item.Arranger }}</li> <li class="list-group-item">作品:{{ item.TieUp}}</li> <li class="list-group-item">ブランド:{{ item.Brand }}</li> <li class="list-group-item">ジャンル: {{ item.Genre }}</li> </ul> <button class="btn btn-outline-info">編集</button> </div> </div> </div> </div> </div>編集ボタンはただの飾りです。今回の条件ではキー設定は特に使わない気もしますがいれないとエラーになるのでいれておきます。
これでitemsというマップが入った配列を1つ1つCardにしていきその中の項目にアクセスできるようになります。そして肝心というかかなり苦戦したScript部分です。
song.vue<script> import axios from "axios"; export default { name: "song", data: function () { return{ type: "曲名から", keyword: "", items: [] } }, methods: { Search: async function (){ this.items = await search2.get(this.type, this.keyword) console.log(this.items) } } } let search2 = { get: async function (type, keyword){ const res = await axios.get(' APIのURL ?title='+keyword) console.log(res.data.Items) return res.data.Items } } </script>ポイントは非同期処理が入っているところです。欲しいのはPromise型ではなく普通の配列なのでSearchメソッド自体もasyncにしてawaitを使う事によりPromise型を普通の配列にしちゃいます。僕は単純な変数の打ち間違いとこれで3日を無駄にしました。
あとデバッグ用にコンソロールにいろいろ表示してますがこれはお好みでどうぞ。コンポーネント全景
song.vue<template> <div> <div class="jumbotron-fluid"> <div class="container"> <h1 class="display-3">楽曲の検索</h1> <p class="lead">Search for song</p> </div> </div> <br> <br> <div> <div class="container"> <form class="form-inline mx-auto" style="width: 500px;"> <div class="form-group"> <select class="form-control" v-model="type"> <option>曲名から</option> <option>アーティスト名から(未実装)</option> </select> <input class="form-control" type="search" placeholder="Search" aria-label="Search" v-model="keyword"> <button class="btn btn-outline-success" type="submit" v-on:click="Search()">Search</button> </div> </form> <div><br>検索は完全一致です<br></div> </div> </div> <div><hr></div> <div> <div class="container"> <div class="row"> <div class="card border-info col-sm-4" v-for="item in items" :key="item.Artist"> <div class="card-body"> <h2 class="card-title">{{ item.Title }}</h2> <ul class="list-group list-group-flush"> <li class="list-group-item">アーティスト名:{{ item.Artist }}</li> <li class="list-group-item">作詞:{{ item.Word }}</li> <li class="list-group-item">作曲:{{ item.Composer }}</li> <li class="list-group-item">編曲:{{ item.Arranger }}</li> <li class="list-group-item">作品:{{ item.TieUp}}</li> <li class="list-group-item">ブランド:{{ item.Brand }}</li> <li class="list-group-item">ジャンル: {{ item.Genre }}</li> </ul> <button class="btn btn-outline-info">編集</button> </div> </div> </div> </div> </div> </div> </template> <script> import axios from "axios"; export default { name: "song", data: function () { return{ type: "曲名から", keyword: "", items: [], list: [] } }, methods: { Search: async function (){ this.items = await search2.get(this.type, this.keyword) console.log(this.items) } } } let search2 = { get: async function (type, keyword){ const res = await axios.get(' APIのURL ?title='+keyword) console.log(res.data.Items) return res.data.Items } } </script> <style scoped> </style>最後に
分かっちゃえば簡単な事でも意外とがっつり躓くことってありますよ。今回はどうしても分からなかったので他人の力を借りようとteratailを使いました。回答してくれた方にこの場を借りてお礼もうしあげます。
https://teratail.com/questions/304455
- 投稿日:2020-11-17T13:53:28+09:00
Vue.jsをはじめて触ってみた
こんにちは。むんです
Vue.jsをはじめて触ってみたので、備忘録として残します
同じ様に、はじめてVue.jsを触る方のご参考になれば嬉しいです。目次
- Vue.jsって何?
- 環境を整える
- プロジェクトの起動
- 簡単なアプリを作成してみる
Vue.jsって何?
Vue.jsとはJavascriptのフレームワークの一種です。
(経緯や特徴など)環境を整える
Vue.jsと使用するには下記3パターンあります。
- CDNをHTMLに書き込む
- npmでVueをインストール
- npmでVue-CLIをインストール
- 直接ダウンロード
今回は、2つ目の「npmでVue-CLIをインストール」する方法でご紹介します。
1.Node.jsをインストール
npmを使うために、まずNode.jsをインストールします。
インストーラーを下Node.js 公式ページから入手して下さい。
※MacだとHomebrew→nodebrew使って入れる方法もあるみたいですが、割愛します。インストールが完了すると、「node」と「npm」のコマンドが使える様になります。
バージョンを確認してみましょう。$ node -v $ v15.2.0 //バージョンが出力される2.npmでVueをインストール
$ npm -v $ v6.14.8 //バージョンが出力されるそれでは、npmを使ってVue-CLIをインストールしましょう。
3.npmでVue-CLIをインストール
$ npm install -g @vue/cli // // 省略 // + @vue/cli@4.5.8 added 1333 packages in 265.59sVue CLI4.5.8がインストールされました!
Vueのバージョンをコマンドで確認しましょう。$ vue -V -bash: vue: command not found上記の様に「command not found」が表示される場合は、パスが通っていない可能性が高いです。
下記のコマンドでnpmのパスが通っているか、確認しましょう。
npm bin -g
「(not in PATH env variable)」と表示される場合、パスが通っていませんので、パスを通しましょう。
export PATH=$PATH:
npm bin -g
>> ~/.bash_profile
もう一度、Vueのバージョンを確認しましょう。$ vue -V @vue/cli 4.5.8成功ですね。
プロジェクトの起動
1.プロジェクトの作成
下記コマンドでプロジェクトを作成します。
vue create {プロジェクト名}
※プロジェクト名は小文字じゃないと怒られます。$ vue create helloworld途中、「Please pick a preset」と聞かれるので、デフォルトを選択。
プロジェクト作成に成功すると、下記の様な画面になります。
2.起動する
cd {プロジェクト名}
npm run service
$ cd helloworld $ npm run service<2.ブラウザで確認する
ブラウザより、下記URLを打ち込んで確認してみましょう。
http:localhost:8080
起動できていれば、下記画面が表示されます。
OKですね。3.フォルダ構成を確認する
各ファイルの解説(ざっくり)
index.html
エントリポイント
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>main.js
index.htmlの「id="app"」部分にAppコンポーネントのDOMツリーを出力しています。
render: h => h(App)
って何だ?と思った方はこちら。(作成中)main.jsimport Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')App.vue
AppコンポーネンのDOMツリーは下記の部分。
App.vue(一部抜粋)<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template>Appコンポーネントのテンプレートから、Helloworldコンポーネントを呼び出しています。
HelloWorld.vue
HelloWorld.vue(一部抜粋)<template> <div class="hello"> <h1>{{ msg }}</h1> <h3>Installed CLI Plugins</h3> <h3>Essential Links</h3> <h3>Ecosystem</h3> </div> </template> <script> export default { name: 'HelloWorld', props: { msg: String } } </script>propsプロパティにmsgプロパティを定義することにより、
親コンポーネント(ここで言うAppコンポーネント)からmsgプロパティを使って、データを受け取る事が可能となります。
ここではmsgプロパティにはAppコンポーネント側で、"Welcome to Your Vue.js App"
が設定されています。デベロッパーツールで確認してみましょう。
index.htmlの「id="app"」の部分に、
AppコンポーネンとのDOMツリーが表示されていることを確認できると思います。簡単なアプリを作成してみる
作りたいもの
2つリンクを用意した簡単なアプリです。(まずはこれくらい優しいのから。)
1.アプリ作成
①まずプロジェクト作成。
vue create linkapp
②「Please pick a preset」と聞かれるので、デフォルトを選択。
③App.vueを編集します。App.vue<template> <div id="app"> <h3>{{ title }}</h3> <ul> <li><a v-bind:href="url1">Vue</a></li> <li><a v-bind:href="url2">Vuetify</a></li> </ul> </div> </template> <script> export default { data(){ return{ title: "Vue sites", url1: "https://vuejs.org/", url2: "https://vuetifyjs.com/" } } } </script>2.アプリ起動
コマンドでアプリを起動して、
$ cd listapp $ npm run serve
http:localhost:8080
にアクセス。
前述の画面が出力され流と思います。少し解説
Vue.jsのコンポーネンとファイル(xxx.vue)の構成は下記の通りになっています。
xxx.vue<template> <!-- テンプレート --> </template> <script> // ロジック </script> <style> /** スタイル **/ </style>scriptをよく見てみましょう。
xxx.vue<script> export default { data(){ return{ // データ } }, methods{ // メソッド }, .... } </script>データの部分は下記の様にプロパティと値を記述します。
xxx.vuedata(){ return{ プロパティ値 : 値, ... } },listappのApp.vueをもう一度確認しましょう。
url1 プロパティ、url2プロパティに値を設定しています。App.vue(ロジック部分)<script> export default { data(){ return{ title: "Vue sites", url1: "https://vuejs.org/", url2: "https://vuetifyjs.com/" } } } </script>Vue.jsのテンプレートでは、
{{}}(マスタッシュ構文)
で保持したデータにアクセスできます。
単にテキストとして表示する場合には{{}}
で良いですが、
hrefなどの属性に対して値を設定する場合は{{}}
は使えないので、
v-bindディレクティブ
を使用します。v-bindディレクティブの表記方法v-bind: 属性="値" または : 属性="値"
:
はv-bind:
の省略形です。
もう一度、テンプレート部分を見てみましょう。xxx.vue<template> <div id="app"> <h3>{{ title }}</h3> <ul> <li><a v-bind:href="url1">Vue</a></li> <li><a v-bind:href="url2">Vuetify</a></li> </ul> </div> </template>省略形だと、こう書けますね。
<li><a :href="url1">Vue</a></li> <li><a :href="url2">Vuetify</a></li>まとめ
Vue.jsを使うための準備から簡単なアプリ作成まで、ざっくりと書いてみました。
いかがだったでしょうか。p.s.今度はディレクティブについて理解を深められる様な記事を書きたいと思っています。
書いたら本記事にリンク貼ります。
- 投稿日:2020-11-17T13:53:28+09:00
はじめてVue.jsを触ってみた
こんにちは。むんです
Vue.jsをはじめて触ってみたので、備忘録として残します
同じ様に、はじめてVue.jsを触る方のご参考になれば嬉しいです。目次
- Vue.jsって何?
- 環境を整える
- プロジェクトの起動
- 簡単なアプリを作成してみる
Vue.jsって何?
Vue.jsとはJavascriptのフレームワークの一種です。
JavascriptのフレームワークはAngularやReactもありますが、
Vue.jsはビューに特化しているそうです。(もう少し分かったら追記します。笑)環境を整える
Vue.jsを使用するには下記3パターンあります。
- CDNをHTMLに書き込む
- npmでVueをインストール
- npmでVue-CLIをインストール
- 直接ダウンロード
今回は、2つ目の「npmでVue-CLIをインストール」する方法でご紹介します。
1.Node.jsをインストール
npmを使うために、まずNode.jsをインストールします。
インストーラーを下Node.js 公式ページから入手して下さい。
※MacだとHomebrew→nodebrew使って入れる方法もあるみたいですが、割愛します。インストールが完了すると、「node」と「npm」のコマンドが使える様になります。
バージョンを確認してみましょう。$ node -v $ v15.2.0 //バージョンが出力される2.npmでVueをインストール
$ npm -v $ v6.14.8 //バージョンが出力されるそれでは、npmを使ってVue-CLIをインストールしましょう。
3.npmでVue-CLIをインストール
$ npm install -g @vue/cli // // 省略 // + @vue/cli@4.5.8 added 1333 packages in 265.59sVue CLI4.5.8がインストールされました!
Vueのバージョンをコマンドで確認しましょう。$ vue -V -bash: vue: command not found上記の様に「command not found」が表示される場合は、パスが通っていない可能性が高いです。
下記のコマンドでnpmのパスが通っているか、確認しましょう。
npm bin -g
「(not in PATH env variable)」と表示される場合、パスが通っていませんので、パスを通しましょう。
export PATH=$PATH:
npm bin -g
>> ~/.bash_profile
もう一度、Vueのバージョンを確認しましょう。$ vue -V @vue/cli 4.5.8成功ですね。
プロジェクトの起動
1.プロジェクトの作成
下記コマンドでプロジェクトを作成します。
vue create {プロジェクト名}
※プロジェクト名は小文字じゃないと怒られます。$ vue create helloworld途中、「Please pick a preset」と聞かれるので、デフォルトを選択。
プロジェクト作成に成功すると、下記の様な画面になります。
2.起動する
cd {プロジェクト名}
npm run service
$ cd helloworld $ npm run service2.ブラウザで確認する
ブラウザより、下記URLを打ち込んで確認してみましょう。
http:localhost:8080
起動できていれば、下記画面が表示されます。
OKですね。3.フォルダ構成を確認する
各ファイルの解説(ざっくり)
index.html
エントリポイント
index.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <link rel="icon" href="<%= BASE_URL %>favicon.ico"> <title><%= htmlWebpackPlugin.options.title %></title> </head> <body> <noscript> <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>main.js
index.htmlの「id="app"」部分にAppコンポーネントのDOMツリーを出力しています。
render: h => h(App)
って何だ?と思った方はこちら。(作成中)main.jsimport Vue from 'vue' import App from './App.vue' Vue.config.productionTip = false new Vue({ render: h => h(App), }).$mount('#app')App.vue
AppコンポーネンのDOMツリーは下記の部分。
App.vue(一部抜粋)<template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png"> <HelloWorld msg="Welcome to Your Vue.js App"/> </div> </template>Appコンポーネントのテンプレートから、Helloworldコンポーネントを呼び出しています。
HelloWorld.vue
HelloWorld.vue(一部抜粋)<template> <div class="hello"> <h1>{{ msg }}</h1> <h3>Installed CLI Plugins</h3> <h3>Essential Links</h3> <h3>Ecosystem</h3> </div> </template> <script> export default { name: 'HelloWorld', props: { msg: String } } </script>propsプロパティにmsgプロパティを定義することにより、
親コンポーネント(ここで言うAppコンポーネント)からmsgプロパティを使って、データを受け取る事が可能となります。
ここではmsgプロパティにはAppコンポーネント側で、"Welcome to Your Vue.js App"
が設定されています。デベロッパーツールで確認してみましょう。
index.htmlの「id="app"」の部分に、
AppコンポーネンとのDOMツリーが表示されていることを確認できると思います。簡単なアプリを作成してみる
作りたいもの
2つリンクを用意した簡単なアプリです。(まずはこれくらい優しいのから。)
1.アプリ作成
①まずプロジェクト作成。
vue create linkapp
②「Please pick a preset」と聞かれるので、デフォルトを選択。
③App.vueを編集します。App.vue<template> <div id="app"> <h3>{{ title }}</h3> <ul> <li><a v-bind:href="url1">Vue</a></li> <li><a v-bind:href="url2">Vuetify</a></li> </ul> </div> </template> <script> export default { data(){ return{ title: "Vue sites", url1: "https://vuejs.org/", url2: "https://vuetifyjs.com/" } } } </script>2.アプリ起動
コマンドでアプリを起動して、
$ cd listapp $ npm run serve
http:localhost:8080
にアクセス。
前述の画面が出力されると思います。少し解説
Vue.jsのコンポーネンとファイル(xxx.vue)の構成は下記の通りになっています。
xxx.vue<template> <!-- テンプレート --> </template> <script> // ロジック </script> <style> /** スタイル **/ </style>scriptをよく見てみましょう。
xxx.vue<script> export default { data(){ return{ // データ } }, methods{ // メソッド }, .... } </script>データの部分は下記の様にプロパティと値を記述します。
xxx.vuedata(){ return{ プロパティ値 : 値, ... } },listappのApp.vueをもう一度確認しましょう。
url1 プロパティ、url2プロパティに値を設定しています。App.vue(ロジック部分)<script> export default { data(){ return{ title: "Vue sites", url1: "https://vuejs.org/", url2: "https://vuetifyjs.com/" } } } </script>Vue.jsのテンプレートでは、
{{}}(マスタッシュ構文)
で保持したデータにアクセスできます。
単にテキストとして表示する場合には{{}}
で良いですが、
hrefなどの属性に対して値を設定する場合は{{}}
は使えないので、
v-bindディレクティブ
を使用します。v-bindディレクティブの表記方法v-bind: 属性="値" または : 属性="値"
:
はv-bind:
の省略形です。
もう一度、テンプレート部分を見てみましょう。xxx.vue<template> <div id="app"> <h3>{{ title }}</h3> <ul> <li><a v-bind:href="url1">Vue</a></li> <li><a v-bind:href="url2">Vuetify</a></li> </ul> </div> </template>省略形だと、こう書けますね。
<li><a :href="url1">Vue</a></li> <li><a :href="url2">Vuetify</a></li>まとめ
Vue.jsを使うための準備から簡単なアプリ作成まで、ざっくりと書いてみました。
いかがだったでしょうか。p.s.今度はディレクティブについて理解を深められる様な記事を書きたいと思っています。
書いたら本記事にリンク貼ります。
- 投稿日:2020-11-17T13:19:43+09:00
Vue.js: 子コンポーネントを親コンポーネントの外で使いたい
Vue.jsで、親コンポーネントに埋め込むために作った子コンポーネントを、親コンポーネントの外側で使いたいことがあります。HTML上で離れたところに置く必要があるときです。
子コンポーネントの例:エラーメッセージ
例として、フォームの送信時に使うようなエラーメッセージのコンポーネントを作りました。
props に title と messages を指定せずに、オブジェクト errors を使っているのは、外側から操作しやすいようにするためです。
alert.vue<template> <div> <div class="alert alert-danger" role="alert" v-if="title || messages.length"> <div v-if="title">{{title}}</div> <ul v-if="messages.length" class="mb-0"> <li v-for="(message, idx) in messages" :key="idx">{{message}}</li> </ul> </div> </div> </template> <script> export default { props: { errors: Object }, computed: { title() { return this.errors.title; }, messages() { return this.errors.messages || []; } } } </script>普通の使い方
まず、普通に親コンポーネントの中に子コンポーネント(エラーメッセージ)を置く使い方です。
sample1.html<div id="sample1"></div>app.jsimport Vue from 'vue'; import Sample1 from './sample1.vue'; document.addEventListener('DOMContentLoaded', () => { new Vue(Sample1).$mount('#sample1'); });sample1.vue<template> <div> <alert :errors="errors"></alert> <p><input type="email" v-model="email" class="form-control" /></p> <p><input type="button" value="送信" @click="submit" class="btn btn-primary" /></p> </div> </template> <script> import Alert from './alert.vue'; export default { components: { 'alert': Alert }, data() { return { email: '', errors: { title: null, messages: [] } }; }, methods: { submit() { this.errors.title = '保存に失敗しました。'; this.errors.messages = ['メールアドレスが不正です。']; } } } </script>外に置くときの使い方
次は、子コンポーネント(エラーメッセージ)を親コンポーネントの外に置くときの使い方です。props 用のオブジェクト errors を作って2つのコンポーネントで共有します。
キモは、Vue.observable を使ってオブジェクトをリアクティブにすることです。
observable
を使わないと title や messages の変化がエラーメッセージに伝わりません。sample2.html<div id="alert"></div> <div id="sample2"></div>app.jsimport Vue from 'vue'; import Alert from './alert.vue'; import Sample2 from './sample2.vue'; document.addEventListener('DOMContentLoaded', () => { const errors = Vue.observable({ title: '', messages: [] }); new Vue({ render: h => h(Alert, { props: { errors: errors } }) }).$mount('#alert'); new Vue({ render: h => h(Sample2, { props: { errors: errors } }) }).$mount('#sample2'); });sample2.vue<template> <div> <p><input type="email" v-model="email" class="form-control" /></p> <p><input type="button" value="送信" @click="submit" class="btn btn-primary" /></p> </div> </template> <script> export default { props: { errors: Object }, data() { return { email: '' }; }, methods: { submit() { this.errors.title = '保存に失敗しました。'; this.errors.messages = ['メールアドレスが不正です。']; } } } </script>なお、Vueのバージョン3では、
observable
の代わりにreactive
を使います。app.jsimport { createApp, reactive, h } from 'vue'; import Alert from './alert.vue'; import Sample2 from './sample2.vue'; document.addEventListener('DOMContentLoaded', () => { const errors = reactive({ title: '', messages: [] }); createApp({ render() { return h(Alert, { errors: errors }); } }).mount('#alert'); createApp({ render() { return h(Sample2, { errors: errors }); } }).mount('#sample2'); });親コンポーネントなしで使う
observable
またはreactive
を使うと、親コンポーネントなしでエラーメッセージを使うこともできます。次は、jQueryを使った例です。sample3.html<div id="alert"></div> <div> <p><input type="email" v-model="email" class="form-control" /></p> <p><input type="button" value="送信" id="submit" class="btn btn-primary" /></p> </div>app.jsimport Vue from 'vue'; import $ from 'jquery'; import Alert from './alert.vue'; document.addEventListener('DOMContentLoaded', () => { const errors = Vue.observable({ title: '', messages: [] }); new Vue({ render: h => h(Alert, { props: { errors: errors } }) }).$mount('#alert'); $('#submit').on('click', (e) => { errors.title = '保存に失敗しました。'; errors.messages = ['メールアドレスが不正です。']; }); });
- 投稿日:2020-11-17T13:18:14+09:00
[VUE]SPAウェブフロントエンド開発ためのまとめ
・Qiita:[VUE]SPAウェブフロントエンド開発ためのまとめ
・原本:[VUE]SPAウェブフロントエンド開発ためのまとめ
目標
本ポスティングではSPASingle Page Application開発に先立ってSPAについての理解およびJavaScriptのフレームワーク「Vue」に対する基本的な理解を目指したいと思います。MPAウェブサービスやMVCパターンに対する基本的な理解を元に作成することになりました。
目次
- JavaScript Frameworkの使用背景
- MPAからSPAまで
- SPAルーティングに対して
①Link方式
②Ajax方式
③Hash方式- JavaScript Frameworkの主な活用の要素
- VUE
- 主要ライブラリ
①VUE Router
②VUE CLI
③Vuex
(1)Stateとは
(2)State管理の必要性
(3)構成要素
(4)State、Getters、Actions、Mutaitions
(5)レンダリングパフォーマンスについて- まとめ
1. JavaScript Frameworkの使用背景
JavaScriptのフレームワークが登場する前には、vanilla JSやjQuery*1をフロントロジックで活用して具現しました。JavaScriptフレームワークはフロントエンドの開発が複雑化によって登場することになりました。
JavaScriptフレームワークを活用すると、SPA(Single Page Application)*2など高級ウェブアプリケーションを開発するのにもっと容易します。*1 jQuery:簡単にDOMを操作するためのライブラリ
*2SPA:単一ページのアプリケーション(Single Page Application)の形でウェブを開発することを意味、詳しい内容は2番を参考
2. MPAからSPAまで
既存のMPA(multiple-page application)ウェブサービスは、クライアントのリクエストrequestがある際ごとにサーバからリソースresourceとデータを受け取って、画面に新たにレンダリングrenderingをするやり方でありました。 リンク
<a href="#">
をクリックすると、該当ページに移動することになり、明示されている資源をサーバーに要請して回答を受けてきます。
このように、MPA方式は各ページごとにサーバ側でhtml文書を要請するために重複されるデータを受け取るしかありませんでした。一方、SPAウェブサービスは、上記のような既存のウェブサービスの限界を補完します。 最初のロード時、全体ページをロードした後からは、既存のページと比較して更新が必要な特定の部分だけをjsonの形でデータを持ってきてバインディングbindingします。
したがって、SPA方式はAjax(3番参照)にデータを持って来てから必要な部分だけ更新するようにJavascriptを作成します。
3. SPAルーティングに対して
*ルーティング(Routing)とは。
データを送ることが最適の経路を選択する過程で、与えられたデータを最適化された形でやり取りできる経路を選択する過程です。①リンク方式(伝統的方式)
リンク方式は下記の過程を含んでいます。
(1) link tag(
<a href="#">
)のクリック
(2) URLのpathにhref属性attribute値であるリソースの経路が追加
(3) ブラウザのアドレスバーにpath値を表示
(4) 該当リソースをサーバに要請クライアント側で(1)~(4)を経ることになると、サーバ側からでは完全なリソースをhtmlの形で回答します。 また、ブラウザ側ではこのhtmlを受けてレンダリングします。 前のページから新たに受けたページまで転換するやり方は、全体のページを再びレンダリングする方式であります。それで、リロードreloadが発生してヒストリーhistoryが残り、前のページに戻ることができます。 この時、クライアントのリクエストごとに重複されたHTMLとJavaScript、CSSについてももらってこなければならないので、パフォーマンスにあって損害がある可能性があります。
出典:ASP.NET–Single-Page Applications / 伝統的なページ寿命周期
②AJAX方式
これを補完するため、JavaScriptで非同期Asynchronousにサーバとブラウザがデータを交換できる通信方式を活用することになり、この通信方式をAJAXAsynchronous JavaScript and XMLと言います。
ページでアップデートが必要な部分だけをロードした後に更新して、リロードしなく不必要なリソースの重複要請を防ぐするので、パフォーマンスにあってもっと優れています。データ部分が空白であるhtmlをサーバーから受け取って、ページロード時JavaScriptでデータを持ってきて満たして入れます。
出典:ASP.NET–Single-Page Applications /SPA寿命周期
この時、AJAXはURLを変更させないので、アドレスバーの住所が変更されません。 これはブラウザの履歴historyを管理することができないし、前のぺーじでもどることもできないことを意味します。 (このほかにもSEOに対してもよくないです。)
③HASH方式
上のAJAXを補完するため、登場したクライアントルーティング方法がHashです。
Hash方式は、Anchor Tagを使う方式で、link tag(
<a href="#hash">
など)のhref属性にhash(#
)を使用します。 クリックする際に文書で#
の後に付けているidを持った要素に移動します。同一のURLでHashが変更された場合
ブラウザ側ではこれをURLが変わったものと認識するが、実際にクライアント側でサーバ側にリクエストを送ったりはしません。 Anchorでウェブページの内部での移動を向けたものだからです。要請をしないため、ページの更新はありませんが、ページがそれぞれの固有のURLを持つので、ヒストリーhistoryを管理することができます。上記の3つの方式のほか、PJAX(html5 pushState+ajax)もあります。
4.Javascript Frameworkからの主な活用点
- ルーティング
:公式的なルーティングライブラリが提供されています。
- テンプレート
:レンダリングされたDOMの上に基本インスタンスのデータを宣言的にバインディングがすることができるHTML基盤テンプレート言語を使えます。
(この部分はdJangoとも似ていたと思います)
- コンポーネントの再利用
WebコンポーネントはWebページとWebアプリケーションの間でカプセル化された再使用ができるHTMLタグを生成することができます。
5.VUE
仮想DOMを活用できるし、DOMを使って全ての要素をリアルタイムでの反応型コンポーネントで製作することができるJavascriptフレームワークです。 通常、プロジェクトの規模が大きくなるほど、様々な形のコンポーネントが生成されるために、これを管理するのに役立つルーティング管理およびグローバル状態を管理するライブラリなどを提供します。
- 他のフレームワークとの比較(公式ドキュメント)
6.主要ライブラリ
①VUE Router
Vueを利用してSPAウェブを構築する際には、ルーティング制御を向けた公式プラグインです。
- VueRouterを利用してURL・ヒストリーを管理することができます。
- ブラウザで
뒤로 가기
及び앞으로 가기
動作をできるようにしており、これは使用性を向上させることができます。[ルーティングを定義する形式]
//views/Home.vue(テンプレートの構成要素)<template> <div class="home"> <h1>Home</h1> </div> </template>//src/router.js(ルーティング定義)import Vue from ' vue ' import Router from ' vue-router ' import Home from ' ./views/ ' Vue . use ( Router ) export default new Router ({ mode : ' history ' , routes : [ { path : ' / ' , name : ' home ' , component : Home} ] })以後、リンクと定義する際には、テンプレートの下記のような構文を挿入して使用することができます。
<router-link to="/">Home</router-link>
②VUE CLICommand Line Interface
Vue-cliにコマンドを実行すると、cliが自動的に最適化された、プロジェクトの基本骨格を生成します。 だけでなく、下記の設定が可能です。
- Vueプロジェクト生成
- Vueライブラリ管理
- Vueの配布ファイルの設定:最適化されたWebpack*1形の結果物を生成します。
- Vue GUI提供
*1Webpack:JS、CSS、イメージを一つのJSファイルで縛りつけられるモジュールです。 開発者が作成したJSとプロジェクト内の必要なライブラリをロードするJS、作成したCSSファイルとイメージファイルまで整理することができます。 イメージファイルの場合、DataURI(Base64形式)に変換してJSファイルに整理します。
*dJangoでプロジェクトを生成して、ライブラリを管理して、配布時に静的ファイル管理を提供したものと類似した感じ
③Vuex
Vueコンポーネントで活用することがデータを管理することを目的とする、ステートState管理ライブラリです。
1)Stateとは
*公式文書でstateデータ管理を状態のパターンに翻訳をしておいて、多くの部分がこんがらがったところ(デザインパターン中にステートパターンを融合させたと)で、mutationも各翻訳によって言葉が異なりました。開発をある程度進行した後、もっと確実に定義することができました。
すべてのクライアント側のコンポーネントで共通的に使えるように選び出したクライアント用データです。 一般的にクライアント側にデータを渡すためには、サーバーからデータを受け取ってこなければならないという煩わしさがあります。 Stateを使う場合、サーバから最初持ってきたデータをStateに保管します。 以降、必要な部分に対するデータだけ取り出して使うことになります。
2)State管理の必要性
MVCパターンの複雑なデータフロー問題は、大規模なアプリケーション開発の際MVCパターンで発生する構造的間違いを引き起こします。 複雑な画面とデータの構成が必要したときControllerに多数のModelとViewが縺れてからです。 したがって、既存のMVC間の依存性を除去することが難しくなって、これは機能追加やアップデート時に起きる問題点を解決する複雑にします。
Vuexは、コンポーネント間のデータ伝達を
View
、Action
、State
に明示的に表現して、よりデータの流れを単純に維持します。3)構成要素
Vuex構成要素は
View, Actions, State
になります。
[例示]
出典:https://vuex.vuejs.org/(vuex公式文書)
(1)ユーザが
View(Template)
でボタンをクリック
(2)Actions(Method)
でメソッド呼び出して動作
(3)Action
s実行してからState(data)
を変更Viewは、クライアントの方に表示されるTemplateを意味します。 クライアント側でViewを通じてデータ保存などの動作を要請すると、要請によって
Actions
ではバックエンド側と要請や応答を交換するようになります。
State
の場合、クライアントの操作に必要なデータを共有します。Actions
を通じてバックエンドAPIを呼び出して要請に即したデータをもらって来たら、コンポーネント間で当該データをすべて共有できるようにState
に保存します。各コンポーネントでは
State
にプロジェクトで共通に共有するデータを保存しておくことができ、最初の保存だけでどのコンポーネントでも当該データを照会することも、修正することもできます。(4)State、Getters、Actions、Mutations
- State
すべてのコンポーネントで同一のデータを使用できるように共通的な変数を宣言します。
- Getters
コンポーネント内でState
に特定処理を進めた後、コンポーネントにバインディングする場合、Getters
に該当ロジックを定義した後、各コンポーネントで呼び出すことができます。
(例)State
に含まれるすべてのデータのうち、特定のデータだけを持ってくる場合
- Actions
回答を受け取ってくる部分のタームが一定しない場合などの大気が必要でない非同期ロジックが入っている処理を作成します。
- Mutations
State
に存在する変更するロジックを作成し、State
の値を変更させることはMutations
中でのみ変更されなければなりません。
しかし、開発者がMutation
乙直接呼び出す場合はなく、State
を変更するcommit
背中のメソッドを使用するなどの方法で間接的に使用することになります。Mutation
の場合、各コンポーネントでState
を変更した履歴を確認することができます。 各変更件について一つ一つ同期的に処理し、全ての履歴を残すので、お互いに違うコンポーネントでState
を変更しても衝突がありません。
(5)レンダリングパフォーマンスについて
Vueのgetters
の場合、使用するたびに、毎回演算処理が入っている方式であるため、多くの量のデータを処理する場合には性能が急激に低下する現象がありました。 コンポーネントから呼び出すデータの数(row基準)が3000件ほどになる時、普段、秒当たり60フレームの性能を見せてくれましたが、たくさんの量のデータを毎回gettersから読んでくると、毎秒2フレームになって最後にはブラウザーが止まってしまいました。
このような現象を改善するため、一番目には
state
に読んでくることをなくして、サーバー側のapiを利用して必要な部分だけを直接読んでくる形で作成しました。state
を通じず、サーバーから直接持ってくるのでサーバー側と連動に間する時間だけかかり、コンポーネントをレンダリングするのは、多くの時間がかかりませんでした。二番目には、該当コンポーネントにページネーションを追加をしました。 最初のデータはすべて読んできた後、クライアント側でページングを処理する形だったためにフレームの低下には大きな役に満たなかったが、ブラウザーが実行中断になってしまう現象が消えました。
7.まとめ
VUEはSPA開発向けのJavascriptフレームワークの一つであります。
- SPA
SPAウェブは、最初のロード時、全体ページをロードした後からは、既存のページと比較して更新が必要な部分だけをjsonの形でデータを持ってきてバインディングbindingします。
- Vue.jsでウェブを開発する際には、プロジェクトの構築及びその他の作業に役に立つ
Vue-Cli
、ルーティングを担当するVue Router
、コンポーネント間のState管理を担当するVuex
を使います。
- Routing
SPAウェブの使用性限界を補完するためには、ルーティングに関するライブラリを利用してヒストリーを管理する必要があります。
- State管理
各コンポーネントではState
にプロジェクトで共通に共有するデータを保存しておくことができ、最初の保存だけでどのコンポーネントでも当該データを照会することも、修正することもできます。
- 投稿日:2020-11-17T13:00:13+09:00
【vue】.syncの使い方。v-modelやセッターを使わずに親子でデータを受けわたす方法。
vueのsync修飾子を使うと、親子でのデータ受け渡しが簡単にできる。
syncの使い方
親と子でそれぞれポイントがある。
親はinputタグなどデータを連動させたいタグに以下を設置する。
:属性名.sync="変数名"
子は①データの受け取り、②データの表示、③変更後のデータの送信の3つの記述を行う。
①データの受け取りexport default { props:{ 親の属性名: {type:型} } }propsで親で指定した属性名を変数として宣言する。
以下でその変数を使用する。②データの表示:value="変数名"inputタグに受け取ったデータを表示する場合は、value属性にv-bindで親から受け取った変数を指定する。
③変更後のデータの送信@input="$emit('update:属性名', $event.target.value)"inputタグで変更があった場合に、親の.syncがついたタグに情報を送る。
この
update:属性名
で指定した値と、親の属性名が一致していること。
フルコード事例
▼フルコード
親<template> <div> <!-- 子テンプレートを呼び出し、プロパティmessageで変数msgを共有 --> <SyncChiled :message.sync="msg" /> <!-- 親で定義しているデータが変更されるかの確認用 --> ・変数msgを表示: {{msg}} </div> </template> <script> import SyncChiled from "./SyncChiled" export default { components:{ SyncChiled //子テンプレをタグとして使えるようにする }, data(){ return{ msg: "初期メッセージ" } } } </script>子(SyncChiled.vue)<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータを指定 --> <input type="text" :value="message" @input="$emit('update:message', $event.target.value)"> </div> </template> <script> export default { //親から受け取ったデータ props:{ message: {type:String} } } </script>▼画面表示例
↓ 入力に合わせて変数も変更されている
- 投稿日:2020-11-17T12:38:34+09:00
【Vue】Avoid mutating a prop directlyエラーの発生原因と対処法
「Avoid mutating a prop directly」といったエラーが出たときの対処法と原因について。
エラー[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "thPosition"[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "thPosition"
発生原因
親から子に渡されたデータで、子の中でそのデータを変更しようとすると発生する。
つまり、propsのデータを変更しようとした時に発生する。
- 子は親からデータ(属性)を
props
として受け取る。- propsは親で定義してあるデータなので、子ではいじれない。
発生事例
親からinputタグのv-modelで渡されたデータは、子にpropsのvalueという変数で渡される。
このvalueを変更しようとした場合に発生する。
親<template> <div> <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 --> <SyncChiled v-model="msg" /> <!-- 親で定義しているデータが変更されるかの確認用 --> ・変数msgを表示: {{msg}} </div> </template> <script> import SyncChiled from "./SyncChiled" export default { components:{ SyncChiled //子テンプレをタグとして使えるようにする }, data(){ return{ msg: "初期メッセージ" } } } </script>子<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="value"> </div> </template> <script> export default { props:{ value: {type:String} } } </script>上記のように子で
v-model="value"
として、propsで定義した変数を変更しようとすると発生する。
対処法
propsのデータを変更する方法
親のデータを子でも変更したい場合がある。そのときは、以下2パターンがよく使われる。
- computedのset()とget()、
- .syncを使う。
1. computedのset()とget()
propsのデータを直接変更することができないため、別の変数を用意し、そこにget()でpropsのデータを入れる。
また、変更を検知して、親にイベントと変更後の値を送る。
子computed: { 変数名: { get() { return this.value; }, set(newVal) { this.$emit('input', newVal); } } }
set(引数) {this.$emit('イベント名', 引数);}
変更後の値を任意の引数で取得し、$emitで親にイベントを送る。
親からv-modelで共有されたデータの場合、イベントハンドラは
@input
となるため、イベント名でinputを指定する。
子フルコード<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="chiledMsg"> </div> </template> <script> export default { props:{ value: {type:String} }, computed: { chiledMsg: { get() { return this.value; }, set(newVal) { this.$emit('input', newVal); } } } } </script>
2. .syncを使う。
sync
は親から受け取ったデータを、propsで定義した変数のみで変更する方法。新しい変数を宣言する必要がないので直感的にわかりやすい。
:属性名.sync = "変数名"
v-bind(:)で属性名を指定し、
.sync
をつけるだけ。
v-bindのみの場合はデータを渡すのみだが、syncをつけることでデータを返せるようになる。
親と子の記述ポイント
親では、inputタグなどのタグ内で、
:属性名
に.sync
を設置する。(親)syncの使い方の例<SyncChiled :message.sync="msg" />子では、(1)propsでデータを受け取り、(2)valueでデータを表示し、(3)inputイベントで変更後の値を戻す。
子<input type="text" :value="message" @input="$emit('update:message', $event.target.value)">特に、inputの記述が重要。$emitで親にイベントと引数を渡す。
渡すイベントは、「update:属性名」これが、親の
:属性名.sync
と連動する。
@input=$emit('update:属性名', $event.target.value)
▼フルコード
親<template> <div> <!-- 子テンプレートを呼び出し、プロパティmessageで変数msgを共有 --> <SyncChiled :message.sync="msg" /> <!-- 親で定義しているデータが変更されるかの確認用 --> ・変数msgを表示: {{msg}} </div> </template> <script> import SyncChiled from "./SyncChiled" export default { components:{ SyncChiled //子テンプレをタグとして使えるようにする }, data(){ return{ msg: "初期メッセージ" } } } </script>子(SyncChiled.vue)<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータを指定 --> <input type="text" :value="message" @input="$emit('update:message', $event.target.value)"> </div> </template> <script> export default { //親から受け取ったデータ props:{ message: {type:String} } } </script>▼画面表示例
↓ 入力に合わせて変数も変更されている
- 投稿日:2020-11-17T11:30:59+09:00
【Vue】v-modelを完全理解!v-modelの基本動作と応用パターン。混乱するパターンのまとめ。
vueで多用するv-modelの理解を深めるため、処理をより基本的なv-bind(:)とv-on(@)で記述してみる。
また、v-model使用時のよくある勘違いについてパターン別にまとめ。
目次
- v-modelでできること
- v-modelを使った記述
- v-modelを使わない書き方(1)
- v-modelを使わない書き方(2)
- v-modelを使った親子間のデータやりとり
- よくある混乱ポイント(親子でのデータ連動)
v-modelでできること
inputタグなどのテキスト入力や、セレクトボックスの選択内容の変更に合わせて変数を変更することができる。
ブラウザの画面表示と裏側のデータを双方向でつなげるため、双方向バインディングと呼ぶ。
▼双方向バインディングのイメージ
- 画面上のUI操作で入力値や選択項目を変更 → 変数も変わる。
- 変数を変える → 画面上の表示が変わる。
例: inputタグで使った場合
inputタグの例でみると、inputタグの表示内容であるvalue属性に変数を指定。
inputイベントで入力内容の変更を取得しその変数に代入する操作となる。
↓ 内容を変更
下部に表示している変数の中身も変化する。
v-modelを使った記述
属性にv-modelを記載し変数を指定するだけ。実にシンプル。
template<input type="text" v-model="msg">v-modelの裏側にはvalue属性が隠れている。このため、上記はinputタグのvalue属性として変数msgを指定したという意味になる。
またinputタグ内の変更を検知する
@input
も隠れている。次章のv-modelを使わない書き方を見るとわかりやすい。
▼フルコードfullコード<template> <div> <input type="text" v-model="msg"> <p>・msg: {{msg}}</p> </div> </template> <script> export default { data(){ return{ msg: "初期メッセージ" } } } </script>
v-modelを使わない書き方(1)
v-modelを使わずに書く場合は、
:value
と@input
を使う。template<input type="text" :value="msg" @input="textChange">入力内容に変化があったらイベントtextChangeが発火。イベントはscriptのmethodsに記載。
scriptmethods:{ textChange(event){ this.msg = event.target.value } }変更内容の値、
event.target.value
を取得し、変数に代入する。・event内容の受け渡し
タグのイベントハンドラで引数を指定しない場合は、メソッドの第一引数で$eventが渡される。▼フルコード
v-modelを使わない<template> <div> <input type="text" :value="msg" @input="textChange"> <p>・msg: {{msg}}</p> </div> </template> <script> export default { data(){ return{ msg: "初期メッセージ" } }, methods:{ textChange(event){ this.msg = event.target.value } } } </script>
v-modelを使わない書き方(2)
上記はメソッドを用意したが、タグ内で変数に代入することも可能。
<input type="text" :value="msg" @input="mas = $event.target.value">この場合、methodsの表記は不要になる。
フルコード<template> <div> <input type="text" :value="msg" @input="mas = $event.target.value"> <p>・msg: {{msg}}</p> </div> </template> <script> export default { data(){ return{ msg: "初期メッセージ" } } } </script>v-modelの裏側では
:value
と@input
属性が隠れていることを知るのが重要。
参考(inputとchange)
ちなみに、inputをchangeに変更すると、テキストを編集後、enterキーをクリック後にメソッドが発火する。
<input type="text" :value="msg" @change="mas = $event.target.value">このようにイベントで処理結果が異なる。
v-modelはこの辺が賢く、
・テキストボックスやtextareaの場合は、
@input
が裏で処理されている形となり、・button関連、チェックボックスやラジオボタン、セレクトボックスの場合は
@change
が裏で処理される形となる。
v-modelを使った親子間のデータやりとり
応用編として、親子間でデータを受けわたす方法を確認する。この場合もv-modelが有効。
大きく、以下3つのパターンを考える。
(1) 親から子にデータを渡すのみ
親から子にデータを渡すのみ。変更内容を親に戻す必要がない場合。(2) 子のみでデータ操作
親のデータを子に渡し、子でデータを編集し、その結果を親に表示する。(3) 親と子でデータ操作
親のデータを子に渡し、親と子の双方でデータをいじれるようにする。
(1) 親から子にデータを渡すのみ
親子それぞれのテンプレートの役割を以下とする。
・親
- dataを定義。
- 子テンプレートを読み込み表示する。・子
- テキストエリアをもつ。
- テキストエリアには親のデータを表示。
- ファイル名は「SyncChiled.vue」。
画面表示イメージ 親のコード<template> <div> <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 --> <SyncChiled v-model="msg" /> ・親で定義した変数: {{msg}} </div> </template> <script> import SyncChiled from "./SyncChiled" export default { components:{ SyncChiled //子テンプレをタグとして使えるようにする }, data(){ return{ msg: "初期メッセージ" } } } </script>▼point
<子テンプレ v-model="msg" />
の役割
- 子にv-modelでデータを渡す。
- データは属性valueとして渡す。(変数名で渡すのではない!)
@input
も設定してあることになるが、今回は使っていない。
@input
を使ってないので、v-model
ではなく、:value
でも同じ動作になる。(v-modelの:value機能のみ使っている)
子(SyncChiled.vue)のコード<template> <!-- 渡すテンプレート --> <div> <!-- 属性valueに、親から受け取った属性(を代入した変数)を代入 --> <input type="text" :value="value"> </div> </template> <script> export default { props:{ value: {type:String} } } </script>▼point
props:{ value }
と
<:value="value">
の理解が重要!
props:{ value }の
props:
- 親から受け取った属性を変数として使えるようにする
- 定義するのは親側で
v-model
と:属性名
で指定したもの- 型や必要性、親から渡されない場合の初期値などの設定も可能
props:{ value }の
value
- v-modelの裏側は
:value
となっているため、親のv-modelで指定した値をvalueとして受け取っている。<:value="value">の
:value=
- inputタグのvalue属性にイコール以下を設定しますという意味
:
をつけることで、変数を指定できる。<:value="value">の
"value"
- propsで定義した変数valueをinputタグの表示内容として使うという意味。
v-modelをややこしくしてるのはこのvalue地獄だと思う。。それぞれの違いを理解することがとても大事なので、一つづつ確認していくことが重要。
(2) 子のみでデータ操作
子でデータを受け取るのみとの違いは、computedプロパティでgetterとsetterを設定すること。
親子それぞれのテンプレートの役割を以下とする。
・親
- dataを定義。
- 子テンプレートを読み込み表示する。
- 子で操作した変更内容が反映されるか確認用に変数を表示する。・子
- テキストエリアをもつ。
- テキストエリアには親のデータを表示。
- データが変更されたら親に出力する。
- ファイル名は「SyncChiled.vue」。
画面のイメージ
親のコード<template> <div> <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 --> <SyncChiled v-model ="msg" /> <!-- 親で定義しているデータが変更されるかの確認用 --> ・変数msgを表示: {{msg}} </div> </template> <script> //子テンプレートをインポート import SyncChiled from "./SyncChiled" export default { components:{ //子をコンポーネントとして宣言(タグとして使えるようにする) SyncChiled }, data(){ return{ //定義したデータ msg: "初期メッセージ" } } } </script>親のコードは変更なし。
▼point
<子テンプレ v-model="msg" />
これが重要
- 子にv-modelでデータを渡す。
- データは属性valueとして渡す。(変数名じゃない!)
@input
も設定してあることになる。
子(SyncChiled.vue)のコード<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="chiledMsg"> </div> </template> <script> export default { props:{ //親から受け取った属性 value: {type:String} }, computed: { //受け取った属性をデータに変換する chiledMsg: { get() { return this.value; }, //変更があったら親に変更内容を渡す set(newVal) { this.$emit('input', newVal); } } } } </script>▼point
<v-model="chiledMsg" />
と
computed: {chiledMsg:{get, set}}
の2つが重要。
- 子の変更内容を検知して、そのデータを取得するため、v-modelを設置する。
- inputタグに親から受け取ったデータを表示し、変更があった場合にその内容を取得して親に戻すために、computedでgetterとsetterを用意する。
- 親にデータを渡すには
$emit
を使う。
- 書き方:
this.$emit('親のイベント名', 渡すデータ)
- 親のv-modelが
@input
イベントも担っている。- v-modelの変数名は親の変数と混乱しないように名前を変えている。(同じでも動く)
(3) 親と子でデータ操作
親と子の双方で変数を変更できるようにするには、親のinputタグにも同じv-modelを設置すればいい。
親<template> <div> <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 --> <SyncChiled v-model="msg" /> <!-- 親の要素 --> <input type="text" v-model="msg"> </div> </template>▼確認用画面イメージ
上側が子の要素。下側が親で設定したinputタグ。
※v-modelの注意点
- v-modelは一つのタグに一つしか使えない。
- 1つのタグ内で複数使いたい場合は、「async」か、「@ inputと:任意の属性」を使う。
- 1つのテンプレートで使えるv-modelで指定できる変数は一つ。
v-modelとcomputedを使わない方法(sync)
.sync
を使うと、v-modelやcomputedを使わずに親子間でデータの受け渡し・変更ができる。
よくある混乱ポイント
親と子の双方にv-modelが必要なの?
双方にv-modelを設置することが必須。
▽親のv-modelの役割
親のv-modelの役割は①子にデータを渡す(value属性として)、②子からデータを受け取る(@ input)としての2役を担っている。
例えば、v-modelを:valueに変更すると、親から子にデータは渡せるが、子からデータを受け取れない。
▽子のv-modelの役割
子のv-modelの役割は①属性valueに変数を代入する(親から受け取ったデータの表示)。②変更内容を取得するの2点になる。
親にデータを渡す操作は、②変更内容を取得後の操作として別途設定している。(※v-model自体が親にデータを渡しているわけではない)
親からのデータ表示のみで、変更する必要がない場合は
v-model
を:value="value"
に変更すればOK。
親でvalueが使われてないのに、なんで子のpropsにvalueが出てくるの?
inputタグ内の
v-model
の裏側が:value
となっているため。
つまり、親にv-modelがあれば、子のpropsにはvalue
が宣言される。逆に、子のpropsにvalueが定義してあれば、親のタグ内に
v-model=""
か:value=""
があるということ。
子のv-modelで指定する値に親の変数名は使えないの?
使えます。
親で定義した変数は親の中で有効。子で定義した変数は子の中で有効。
それぞれ異なる変数なので(内容は同じだが変数自体はイコールではない)、子では別の変数名を設定することが多い。
▼例
親で次のようなv-modelが設定されている場合、
<子テンプレ v-model="msg" />
以下2つの子テンプレートは同じ処理になる。
子1(親と異なる変数名を使用)<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="chiledMsg"> </div> </template> <script> export default { props:{ value: {type:String} }, computed: { chiledMsg: { get() { return this.value; }, set(newVal) { this.$emit('input', newVal); } } } } </script>子2(親と同じ変数名を使用)<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="msg"> </div> </template> <script> export default { props:{ value: {type:String} }, computed: { msg: { get() { return this.value; }, set(newVal) { this.$emit('input', newVal); } } } } </script>ポイントは親の
<子テンプレ v-model="msg" />
で子に渡しているのが、変数msgではなく、v-modelの裏に隠されたvalue属性だということ。つまり、子の中にはmsgが登場していないので、新たに宣言しても重複にならない。
なぜcomputedが必要なのか?
computedでgetterとsetterを設定する理由は、これがない場合を考えるとわかりやすい。
computedがないと、テキスト変更時にpropsで受け取ったvalueを直接変更することになる。
しかし、valueは子のテンプレートに定義しておらず、親のデータとなるため、直接編集できない(エラーが出る)
▽親のデータを変更するための処理
変更を可能にするには、
(1)子で変更を検知
(2)親に変更内容をイベント(属性)として飛ばす
(3)親がイベントを実行し変数を変更する。という手順を踏む必要がある。この処理を記述するためにcomputedが必要。
この親へのデータ送信処理を記述するのがset()。
同時にこの変数には、親から受け取ったデータの内容を代入する必要がある。この処理を行うのがget()。
get()とset()はセットで記述しないと目的の役割を果たさない。このためどちらかがないとエラーが発生する。
▽setを記述しない場合のエラー
・エラー内容
[Vue warn]: Getter is missing for computed property "変数名".▽getを記述しない場合のエラー
・エラー内容
[Vue warn]: Computed property "変数名" was assigned to but it has no setter.変数名にはcomputedで指定した名前が入る
エラー(Property or method "変数名" is not defined)
以下のような長めのエラーが発生する場合もある。
これは、computedを設定せずに、親から受け取った(propsで定義した)データを直接変更しようとしたために発生するエラー。
computedでgetterとsetterを記述することで解消できる。
[Vue warn]: Property or method "変数名" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
「指定した変数名が宣言されてない。dataオプションで定義が必要です」と教えてくれている。
どういう場合に発生しやすいか?
単純に変数を宣言してない場合に発生するが、特にv-modelでは、
親のv-modelで指定した変数を、子のv-modelに記述して、変数同士をつないだ!という勘違いをした場合に発生する。
親から子に渡すのは、親で指定した変数ではなく、v-modelの裏側に隠れたvalue属性の方。
子のpropsで宣言するのも「親からvalue属性を受け取り変数として使用します」ということなので、子の中に親で使っている変数は定義されていない。
エラー(Avoid mutating a prop directly)
長めのエラーでAvoid mutating a prop directlyが発生することもある。
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "変数名"
これは、親で定義した変数を直接変更しようとした場合に発生する。
子テンプレで、propsでvalueを親から受け取り、v-model="value"としてしまった場合など。
エラー内容が実に端的にその内容を表している。
▼エラー内容(日本語ver)
- (親から受け取ったデータ)propを直接変更しちゃダメ。親のテンプレ読み込んだ時にデータ上書きしちゃうよ。
- propのデータじゃなくて、dataかcomputedを指定してね。
- 変更しようとしてる変数はこれだよ:"(propsの)変数名"。
親切、、だけど文章が長すぎて読む気なくすパターン。エラー内容は1文づつちゃんと見よう。。
- 投稿日:2020-11-17T11:30:59+09:00
【Vue】v-modelを完全理解!v-modelの基本動作と応用パターン。混乱するパターンのまとめと対処法。
vueで多用するv-modelの理解を深めるため、処理をより基本的なv-bind(:)とv-on(@)で記述してみる。
また、v-model使用時のよくある勘違いについてパターン別にまとめ。
目次
- v-modelでできること
- v-modelを使った記述
- v-modelを使わない書き方(1)
- v-modelを使わない書き方(2)
- v-modelを使った親子間のデータやりとり
- よくある混乱ポイント(親子でのデータ連動)
v-modelでできること
inputタグなどのテキスト入力や、セレクトボックスの選択内容の変更に合わせて変数を変更することができる。
ブラウザの画面表示と裏側のデータを双方向でつなげるため、双方向バインディングと呼ぶ。
▼双方向バインディングのイメージ
- 画面上のUI操作で入力値や選択項目を変更 → 変数も変わる。
- 変数を変える → 画面上の表示が変わる。
例: inputタグで使った場合
inputタグの例でみると、inputタグの表示内容であるvalue属性に変数を指定。
inputイベントで入力内容の変更を取得しその変数に代入する操作となる。
↓ 内容を変更
下部に表示している変数の中身も変化する。
v-modelを使った記述
属性にv-modelを記載し変数を指定するだけ。実にシンプル。
template<input type="text" v-model="msg">v-modelの裏側にはvalue属性が隠れている。このため、上記はinputタグのvalue属性として変数msgを指定したという意味になる。
またinputタグ内の変更を検知する
@input
も隠れている。次章のv-modelを使わない書き方を見るとわかりやすい。
▼フルコードfullコード<template> <div> <input type="text" v-model="msg"> <p>・msg: {{msg}}</p> </div> </template> <script> export default { data(){ return{ msg: "初期メッセージ" } } } </script>
v-modelを使わない書き方(1)
v-modelを使わずに書く場合は、
:value
と@input
を使う。template<input type="text" :value="msg" @input="textChange">入力内容に変化があったらイベントtextChangeが発火。イベントはscriptのmethodsに記載。
scriptmethods:{ textChange(event){ this.msg = event.target.value } }変更内容の値、
event.target.value
を取得し、変数に代入する。・event内容の受け渡し
タグのイベントハンドラで引数を指定しない場合は、メソッドの第一引数で$eventが渡される。▼フルコード
v-modelを使わない<template> <div> <input type="text" :value="msg" @input="textChange"> <p>・msg: {{msg}}</p> </div> </template> <script> export default { data(){ return{ msg: "初期メッセージ" } }, methods:{ textChange(event){ this.msg = event.target.value } } } </script>
v-modelを使わない書き方(2)
上記はメソッドを用意したが、タグ内で変数に代入することも可能。
<input type="text" :value="msg" @input="mas = $event.target.value">この場合、methodsの表記は不要になる。
フルコード<template> <div> <input type="text" :value="msg" @input="mas = $event.target.value"> <p>・msg: {{msg}}</p> </div> </template> <script> export default { data(){ return{ msg: "初期メッセージ" } } } </script>v-modelの裏側では
:value
と@input
属性が隠れていることを知るのが重要。
参考(inputとchange)
ちなみに、inputをchangeに変更すると、テキストを編集後、enterキーをクリック後にメソッドが発火する。
<input type="text" :value="msg" @change="mas = $event.target.value">このようにイベントで処理結果が異なる。
v-modelはこの辺が賢く、
・テキストボックスやtextareaの場合は、
@input
が裏で処理されている形となり、・button関連、チェックボックスやラジオボタン、セレクトボックスの場合は
@change
が裏で処理される形となる。
v-modelを使った親子間のデータやりとり
応用編として、親子間でデータを受けわたす方法を確認する。この場合もv-modelが有効。
大きく、以下3つのパターンを考える。
(1) 親から子にデータを渡すのみ
親から子にデータを渡すのみ。変更内容を親に戻す必要がない場合。(2) 子のみでデータ操作
親のデータを子に渡し、子でデータを編集し、その結果を親に表示する。(3) 親と子でデータ操作
親のデータを子に渡し、親と子の双方でデータをいじれるようにする。
(1) 親から子にデータを渡すのみ
親子それぞれのテンプレートの役割を以下とする。
・親
- dataを定義。
- 子テンプレートを読み込み表示する。・子
- テキストエリアをもつ。
- テキストエリアには親のデータを表示。
- ファイル名は「SyncChiled.vue」。
画面表示イメージ 親のコード<template> <div> <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 --> <SyncChiled v-model="msg" /> ・親で定義した変数: {{msg}} </div> </template> <script> import SyncChiled from "./SyncChiled" export default { components:{ SyncChiled //子テンプレをタグとして使えるようにする }, data(){ return{ msg: "初期メッセージ" } } } </script>▼point
<子テンプレ v-model="msg" />
の役割
- 子にv-modelでデータを渡す。
- データは属性valueとして渡す。(変数名で渡すのではない!)
@input
も設定してあることになるが、今回は使っていない。
@input
を使ってないので、v-model
ではなく、:value
でも同じ動作になる。(v-modelの:value機能のみ使っている)
子(SyncChiled.vue)のコード<template> <!-- 渡すテンプレート --> <div> <!-- 属性valueに、親から受け取った属性(を代入した変数)を代入 --> <input type="text" :value="value"> </div> </template> <script> export default { props:{ value: {type:String} } } </script>▼point
props:{ value }
と
<:value="value">
の理解が重要!
props:{ value }の
props:
- 親から受け取った属性を変数として使えるようにする
- 定義するのは親側で
v-model
と:属性名
で指定したもの- 型や必要性、親から渡されない場合の初期値などの設定も可能
props:{ value }の
value
- v-modelの裏側は
:value
となっているため、親のv-modelで指定した値をvalueとして受け取っている。<:value="value">の
:value=
- inputタグのvalue属性にイコール以下を設定しますという意味
:
をつけることで、変数を指定できる。<:value="value">の
"value"
- propsで定義した変数valueをinputタグの表示内容として使うという意味。
v-modelをややこしくしてるのはこのvalue地獄だと思う。。それぞれの違いを理解することがとても大事なので、一つづつ確認していくことが重要。
(2) 子のみでデータ操作
子でデータを受け取るのみとの違いは、computedプロパティでgetterとsetterを設定すること。
親子それぞれのテンプレートの役割を以下とする。
・親
- dataを定義。
- 子テンプレートを読み込み表示する。
- 子で操作した変更内容が反映されるか確認用に変数を表示する。・子
- テキストエリアをもつ。
- テキストエリアには親のデータを表示。
- データが変更されたら親に出力する。
- ファイル名は「SyncChiled.vue」。
画面のイメージ
親のコード<template> <div> <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 --> <SyncChiled v-model ="msg" /> <!-- 親で定義しているデータが変更されるかの確認用 --> ・変数msgを表示: {{msg}} </div> </template> <script> //子テンプレートをインポート import SyncChiled from "./SyncChiled" export default { components:{ //子をコンポーネントとして宣言(タグとして使えるようにする) SyncChiled }, data(){ return{ //定義したデータ msg: "初期メッセージ" } } } </script>親のコードは変更なし。
▼point
<子テンプレ v-model="msg" />
これが重要
- 子にv-modelでデータを渡す。
- データは属性valueとして渡す。(変数名じゃない!)
@input
も設定してあることになる。
子(SyncChiled.vue)のコード<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="chiledMsg"> </div> </template> <script> export default { props:{ //親から受け取った属性 value: {type:String} }, computed: { //受け取った属性をデータに変換する chiledMsg: { get() { return this.value; }, //変更があったら親に変更内容を渡す set(newVal) { this.$emit('input', newVal); } } } } </script>▼point
<v-model="chiledMsg" />
と
computed: {chiledMsg:{get, set}}
の2つが重要。
- 子の変更内容を検知して、そのデータを取得するため、v-modelを設置する。
- inputタグに親から受け取ったデータを表示し、変更があった場合にその内容を取得して親に戻すために、computedでgetterとsetterを用意する。
- 親にデータを渡すには
$emit
を使う。
- 書き方:
this.$emit('親のイベント名', 渡すデータ)
- 親のv-modelが
@input
イベントも担っている。- v-modelの変数名は親の変数と混乱しないように名前を変えている。(同じでも動く)
(3) 親と子でデータ操作
親と子の双方で変数を変更できるようにするには、親のinputタグにも同じv-modelを設置すればいい。
親<template> <div> <!-- 子テンプレートを呼び出し、プロパティvalueで変数msgを共有 --> <SyncChiled v-model="msg" /> <!-- 親の要素 --> <input type="text" v-model="msg"> </div> </template>▼確認用画面イメージ
上側が子の要素。下側が親で設定したinputタグ。
※v-modelの注意点
- v-modelは一つのタグに一つしか使えない。
- 1つのタグ内で複数使いたい場合は、「async」か、「@ inputと:任意の属性」を使う。
- 1つのテンプレートで使えるv-modelで指定できる変数は一つ。
v-modelとcomputedを使わない方法(sync)
.sync
を使うと、v-modelやcomputedを使わずに親子間でデータの受け渡し・変更ができる。
よくある混乱ポイント
親と子の双方にv-modelが必要なの?
双方にv-modelを設置することが必須。
▽親のv-modelの役割
親のv-modelの役割は①子にデータを渡す(value属性として)、②子からデータを受け取る(@ input)としての2役を担っている。
例えば、v-modelを:valueに変更すると、親から子にデータは渡せるが、子からデータを受け取れない。
▽子のv-modelの役割
子のv-modelの役割は①属性valueに変数を代入する(親から受け取ったデータの表示)。②変更内容を取得するの2点になる。
親にデータを渡す操作は、②変更内容を取得後の操作として別途設定している。(※v-model自体が親にデータを渡しているわけではない)
親からのデータ表示のみで、変更する必要がない場合は
v-model
を:value="value"
に変更すればOK。
親でvalueが使われてないのに、なんで子のpropsにvalueが出てくるの?
inputタグ内の
v-model
の裏側が:value
となっているため。
つまり、親にv-modelがあれば、子のpropsにはvalue
が宣言される。逆に、子のpropsにvalueが定義してあれば、親のタグ内に
v-model=""
か:value=""
があるということ。
子のv-modelで指定する値に親の変数名は使えないの?
使えます。
親で定義した変数は親の中で有効。子で定義した変数は子の中で有効。
それぞれ異なる変数なので(内容は同じだが変数自体はイコールではない)、子では別の変数名を設定することが多い。
▼例
親で次のようなv-modelが設定されている場合、
<子テンプレ v-model="msg" />
以下2つの子テンプレートは同じ処理になる。
子1(親と異なる変数名を使用)<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="chiledMsg"> </div> </template> <script> export default { props:{ value: {type:String} }, computed: { chiledMsg: { get() { return this.value; }, set(newVal) { this.$emit('input', newVal); } } } } </script>子2(親と同じ変数名を使用)<template> <!-- 渡すテンプレート --> <div> <!-- 親に戻すデータをv-modelで指定 --> <input type="text" v-model="msg"> </div> </template> <script> export default { props:{ value: {type:String} }, computed: { msg: { get() { return this.value; }, set(newVal) { this.$emit('input', newVal); } } } } </script>ポイントは親の
<子テンプレ v-model="msg" />
で子に渡しているのが、変数msgではなく、v-modelの裏に隠されたvalue属性だということ。つまり、子の中にはmsgが登場していないので、新たに宣言しても重複にならない。
なぜcomputedが必要なのか?
computedでgetterとsetterを設定する理由は、これがない場合を考えるとわかりやすい。
computedがないと、テキスト変更時にpropsで受け取ったvalueを直接変更することになる。
しかし、valueは子のテンプレートに定義しておらず、親のデータとなるため、直接編集できない(エラーが出る)
▽親のデータを変更するための処理
変更を可能にするには、
(1)子で変更を検知
(2)親に変更内容をイベント(属性)として飛ばす
(3)親がイベントを実行し変数を変更する。という手順を踏む必要がある。この処理を記述するためにcomputedが必要。
この親へのデータ送信処理を記述するのがset()。
同時にこの変数には、親から受け取ったデータの内容を代入する必要がある。この処理を行うのがget()。
get()とset()はセットで記述しないと目的の役割を果たさない。このためどちらかがないとエラーが発生する。
▽setを記述しない場合のエラー
・エラー内容
[Vue warn]: Getter is missing for computed property "変数名".▽getを記述しない場合のエラー
・エラー内容
[Vue warn]: Computed property "変数名" was assigned to but it has no setter.変数名にはcomputedで指定した名前が入る
エラー(Property or method "変数名" is not defined)
以下のような長めのエラーが発生する場合もある。
これは、computedを設定せずに、親から受け取った(propsで定義した)データを直接変更しようとしたために発生するエラー。
computedでgetterとsetterを記述することで解消できる。
[Vue warn]: Property or method "変数名" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
「指定した変数名が宣言されてない。dataオプションで定義が必要です」と教えてくれている。
どういう場合に発生しやすいか?
単純に変数を宣言してない場合に発生するが、特にv-modelでは、
親のv-modelで指定した変数を、子のv-modelに記述して、変数同士をつないだ!という勘違いをした場合に発生する。
親から子に渡すのは、親で指定した変数ではなく、v-modelの裏側に隠れたvalue属性の方。
子のpropsで宣言するのも「親からvalue属性を受け取り変数として使用します」ということなので、子の中に親で使っている変数は定義されていない。
エラー(Avoid mutating a prop directly)
長めのエラーでAvoid mutating a prop directlyが発生することもある。
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "変数名"
これは、親で定義した変数を直接変更しようとした場合に発生する。
子テンプレで、propsでvalueを親から受け取り、v-model="value"としてしまった場合など。
エラー内容が実に端的にその内容を表している。
▼エラー内容(日本語ver)
- (親から受け取ったデータ)propを直接変更しちゃダメ。親のテンプレ読み込んだ時にデータ上書きしちゃうよ。
- propのデータじゃなくて、dataかcomputedを指定してね。
- 変更しようとしてる変数はこれだよ:"(propsの)変数名"。
親切、、だけど文章が長すぎて読む気なくすパターン。エラー内容は1文づつちゃんと見よう。。
- 投稿日:2020-11-17T10:56:26+09:00
Vue.js 表示文字列にURLが含まれていたときにリンク化する
目的
- Vue.jsにおいて表示する文字列の中にURLが含まれていた場合リンク化して表示する方法を簡単にまとめる
詳細
下記のPタグの中に変数textの内容を格納する。
vue.js<p v-html="text"></p>変数testにURLが含まれている場合、リンクとして表示したい。下記のようなメソッドを定義する。
vue.js/** * @param {String} text */ autoLink(text) { return _.isString(text) ? text.replace(/(https?:\/\/[^\s]*)/g, "<a href='$1'>$1</a>: ''; },下記のようにPタグ部分で定義したメソッドを呼ぶ。
vue.js<p v-html="authLink(text)"></p>メソッド部分の簡単な解説
下記にメソッドの処理部分を記載する。
vue.jsreturn _.isString(text) ? text.replace(/(https?:\/\/[^\s]*)/g, "<a href='$1'>$1</a>": '';
isString(text)? : ;
- 参考演算子を用いて引数に文字列が格納されているかチェックしている。
isString(引数文字列)? isStringが真のときの処理 : isStringが偽のときの処理
のようになる。
text.replace(/(https?:\/\/[^\s]*)/g, "<a href='$1'>$1</a>"
- replaceメソッドを使用して文字列の置換を行っている。
- replace(検索条件の文字列, 置換後の文字列)として指定している。
/(https?:\/\/[^\s]*)/g
(検索条件文字列の正規表現)
- 正規表現部分を細かくまとめてみる。
- 最初と最後の
/
はデリミタ(区切り文字)といって「この部分が正規表現ですよ」ということを表している。- 最後の
/
のあとのg
は直前の正規表現を繰り返し検索する修飾子である。- デリミタの最初から最後部分までの
( )
は正規表現部分が一つの文字列であることを表している。- 「https」の後ろの
?
は直前の文字があるかないかでヒットとなる。なのでhttpでもhttpsでもヒットするように正規表現を用いて記述している。:\/\/
は「://」という文字列をエスケープを使用して記載しているだけである。[ ]
は内部に記載された内容いずれかにヒットする。^
は後述する内容以外にヒットする。\s
は空白文字、半角スペース、タブ、改行文字のいずれかにヒットする。*
は直前文字0回以上繰り返すときにヒットする。(URLのあとに半角スペース、全角スペース、改行が入るところで区切る)上記に記載した内容を日本語にしてまとめてみる。
/(httpかhttpsにマッチ://空白文字、半角スペース、タブ、改行文字以外のいずれかにマッチする。直前の文字列が0回以上マッチする。)/
<a href='$1'>$1</a>
- 置換後の文字列であり$1にはマッチした文字列が格納されている。
- 投稿日:2020-11-17T08:26:31+09:00
YouTube動画をキュレーションしてみたかった
私は比較的YouTubeの動画をよく見るのですが、自分が好きな動画のジャンルがチャンネル問わずにトピックごとにまとめられていると便利だろうと常々考えていました。そこで、自分のフロントエンド技術向上も兼ねて、自分用にYouTubeの動画をキュレーションしてくれるWebアプリを作ってみました。
成果物
完成したWebアプリは、Herokuにデプロイして公開しています。(https://youtubecurator.herokuapp.com/)
モバイル向けのデザインにしていること、自分が好きなジャンル(筋トレと将棋)に限られているので一般向けではないことにご注意ください。また、手持ちのiPhoneでしか動作確認していませんので、Android等では不具合が生じるかもしれません。こちらもご了承ください。また、本アプリのソースコードも公開していますので興味のある方はご覧ください。
(https://github.com/ufield/youtube-curator-open)アプリの紹介
アプリは、トピックごとに動画を分類し、縦スクロールで切り替えられます。また、トピック内では横スクロールで動画を選ぶことができます。動画を選択すると上部のYouTube動画が切り替わります。実際に上部のYouTube動画をタップすると、動画が再生されます。
画面右上の検索ボタンをタップすると、検索オプションが表示されます。必要なトピックのみを選択できたり、検索期間の変更が可能です。
画面右上の右から2番目のボタンをタップすると、動画のジャンルを切り替えることができます。
システム全体の概説
今回作成したアプリの全体像を概説します。上で紹介した公開中のWebアプリは、下図中のYouTube Curator WebAppとなります。
YouTube Data APIによって目的のジャンル・チャンネルの動画を取得し、Firebase の Cloud Firestore にデータを蓄積します。この際、どのチャンネルをどのようなトピックでキュレーションするかの定義ファイルを作成しておき、それを使用することで Cloud Firestore では適切なデータ構造としておきます。(基本的に動画タイトルに基づいてトピックに紐付けを行います)
YouTube Curator WebApp は、Cloud Firestoreからデータを取得して上記の紹介のように表示を行います。アノテーション用WebAppは、適切にトピック付けがなされていなかった場合に、手動で簡単にトピックを付け替えるための管理者用アプリです。下図のような管理画面から、各動画ごとのトピックを変更して、それを Cloud Firestore に反映させることができます。(管理向けのため公開はしていません)
今回は、YouTube Curator WebApp・アノテーション用WebApp ともに Nuxt.js + Vuetifyという組み合わせで作成したみました。初めて触ってみましたが、なかなか作りやすかったように思います。また、UI作成のデザインパターンとして、アトミックデザインを採用しています。アトミックデザインは様々なところで解説されていますが、具体例については、例えば過去の私の記事(OpenCV をビジュアルプログラミングできるアプリを Electron + Vue.js で作成)などを参考にしていただけると幸いです。
終わりに
今回は、Firebase, Nuxt.js, Vuetify などを初めて使用しましたが、それなりに動くものが作れてよかったです。完全に自分向けなので、動画のジャンルは管理者の決め打ちになってしまってますが、これをユーザーが各々カスタマイズできるようなアプリにしたら面白そうだ(けど、コストかかりそうだなあ...)とか思っています。仮にものすごい反響があれば考えてみたいと思います。
- 投稿日:2020-11-17T08:26:31+09:00
YouTube動画をキュレーションしてみた
私は比較的YouTubeの動画をよく見るのですが、自分が好きな動画のジャンルがチャンネル問わずにトピックごとにまとめられていると便利だろうと常々考えていました。そこで、自分のフロントエンド技術向上も兼ねて、自分用にYouTubeの動画をキュレーションしてくれるWebアプリを作ってみました。
成果物
完成したWebアプリは、Herokuにデプロイして公開しています。(https://youtubecurator.herokuapp.com/)
モバイル向けのデザインにしていること、自分が好きなジャンル(筋トレと将棋)に限られているので一般向けではないことにご注意ください。また、手持ちのiPhoneでしか動作確認していませんので、Android等では不具合が生じるかもしれません。こちらもご了承ください。また、本アプリのソースコードも公開していますので興味のある方はご覧ください。
(https://github.com/ufield/youtube-curator-open)アプリの紹介
アプリは、トピックごとに動画を分類し、縦スクロールで切り替えられます。また、トピック内では横スクロールで動画を選ぶことができます。動画を選択すると上部のYouTube動画が切り替わります。実際に上部のYouTube動画をタップすると、動画が再生されます。
画面右上の検索ボタンをタップすると、検索オプションが表示されます。必要なトピックのみを選択できたり、検索期間の変更が可能です。
画面右上の右から2番目のボタンをタップすると、動画のジャンルを切り替えることができます。
システム全体の概説
今回作成したアプリの全体像を概説します。上で紹介した公開中のWebアプリは、下図中のYouTube Curator WebAppとなります。
YouTube Data APIによって目的のジャンル・チャンネルの動画を取得し、Firebase の Cloud Firestore にデータを蓄積します。この際、どのチャンネルをどのようなトピックでキュレーションするかの定義ファイルを作成しておき、それを使用することで Cloud Firestore では適切なデータ構造としておきます。(基本的に動画タイトルに基づいてトピックに紐付けを行います)
YouTube Curator WebApp は、Cloud Firestoreからデータを取得して上記の紹介のように表示を行います。アノテーション用WebAppは、適切にトピック付けがなされていなかった場合に、手動で簡単にトピックを付け替えるための管理者用アプリです。下図のような管理画面から、各動画ごとのトピックを変更して、それを Cloud Firestore に反映させることができます。(管理向けのため公開はしていません)
今回は、YouTube Curator WebApp・アノテーション用WebApp ともに Nuxt.js + Vuetifyという組み合わせで作成したみました。初めて触ってみましたが、なかなか作りやすかったように思います。また、UI作成のデザインパターンとして、アトミックデザインを採用しています。アトミックデザインは様々なところで解説されていますが、具体例については、例えば過去の私の記事(OpenCV をビジュアルプログラミングできるアプリを Electron + Vue.js で作成)などを参考にしていただけると幸いです。
終わりに
今回は、Firebase, Nuxt.js, Vuetify などを初めて使用しましたが、それなりに動くものが作れてよかったです。完全に自分向けなので、動画のジャンルは管理者の決め打ちになってしまってますが、これをユーザーが各々カスタマイズできるようなアプリにしたら面白そうだ(けど、コストかかりそうだなあ...)とか思っています。仮にものすごい反響があれば考えてみたいと思います。
- 投稿日:2020-11-17T02:07:55+09:00
[Vue.js]関数の引数
関数に引数を与える
<p>現在{{num}}回クリックされました</p> //クリックするたび2足される <button v-on:click = "countUp(2)">クリック</button>js data:{num:0} methods:{ //これはjsと同じで因数を下の式に当てはめているだけ countUp:function(a){ this.num+=a }関数の引数eventをhtmlに渡す場合
//マウスのX軸とY軸を表示 <p v-on:mousemove = "mousePosition($event,5)">マウスのX軸とY軸を表示するよ</p> <p>x:{{x}}y:{{y}}</p>data:{x:0,y:0}, methods:{ //マウスの場所xとyに対して渡した数を割る mousePosition:function(event,divide){ this.x = event.clientX / divide; this.y = event.clientY / divide; }eventをhtmlに渡す場合 $event として渡す