- 投稿日:2021-01-24T22:45:05+09:00
Vue.jsのwatch機能のまとめ
データ変更の監視機能をもつ"watch"についてメモしたいと思います。
vueの機能には,
data,methods,computedといった機能をよく使いますが、watchという"dataやcomputedのプロパティの値を監視して、変更がある時に任意の処理を実行させることができる機能です。基本的な使い方
【dataの値を監視】
下記のようにwatchオブジェクトの中で、
監視したい変数:
function(next(変更後の値の引数)、prev(変更前の値の引数))と宣言することで、監視したい変数に変更があった場合には関数が実行されます。
注意したいのはv-modelを使ったテキスト入力などは一文字ずつ反映されてしまうので、一文字ずつ入力するたびに反応してしまうので.lazyなど修飾子をうまく活用しましょう。
実際に現場で使っているかはわかりませんが、非同期処理やコストの高い処理に最も便利かもしれません。JavaScript"sampel.js"Vue.createApp({ data:function(){ return{ message:'', } }, watch:{ message: function(next, prev){ console.log('next:' + next ) console.log('prev:' + prev) },html"sample.html<div id ="app"> <div> <input type="text" v-model.lzy="todoTitle" /> <p> {{ todoTitle }} </p> </div> </div>【computedの値を監視する】
watchはdataのみだけでなく、computedの値も監視することが可能です。使い方はdataと同じように
監視したい関数:function(今回の実行結果、前回の実行結果)
と言うような形で指定することで監視することが可能です。
data: { //省略 }, watch: { todoTitletext: function(next, prev){ console.log('next:' +next); console.log('prev:' +prev); } }, computed:{ todoTitletext: function(){ return this.todoTitlemessage= '入力は' + this.todoTitle },と言うような感じです。
ちょっと応用編
配列内のオブジェクトのデータ監視について
よくよくアプリケーションの構築を考えてみると、単純なテキスト入力よりもオブジェクトで利用している場合がほとんどかと思います。
そういった場合には、基本的なやり方では検知してくれないので、、//todosのオブジェクトの中身を検知したい!! watch: { todos:{ handler:funciton(next,prev){ todoTitletext: function(next, prev){ console.log('todos変更あり') }, deep: true, } },といった感じで、
1.todosのオブジェクトを指定
2.オブジェクトの中でhandlerプロパティに関数を指定
3.deepプロパティを"true"に設定
することで監視しているプロパティのネストの深さに関係なく変更を検知できるようになります。
通常は値の変更はcomputedやメソッドで確認しているので、やはり非同期処理などで使うかもですね。。。使用例【メモ】
watch関数を使ってローカルストレージへの保存
create機能を使ってローカルストレージからの呼び出しwatch:{ todos:{ handler:function(next){ window.localStorage.setItem('todos',JSON.stringify(next)) }, deep:true, }, categories:{ handler:function(next){ window.localStorage.setItem('categories',JSON.stringify(next)) }, deep:true, } }, created: function() { const todos = window.localStorage.getItem('todos') const categories = window.localStorage.getItem('categories') if(todos){ this.todos =JSON.parse(todos) } if(categories){ this.categories = JSON.parse(categories) } },
- 投稿日:2021-01-24T21:38:58+09:00
Vue.js 単一ファイルコンポーネントのテストを書いてみたメモ
Vue Test Utilsを使ったvue.jsの単一ファイルコンポーネントのテストに興味があったのですが、あまり書く機会がなかったので今回個人的に書いてみた感じのメモです。
はじめに
vueは2系、使用したテストランナーはjestです。
Vue Test Utils : テストランナを選ぶ他、typeScriptを使用、またVuexの注入について軽く触れます。
また、セットアップの流れは公式に詳しいのですが、個人的にパッケージ周りとjestの設定について調べてみてわかったことを書きます。
セットアップ
パッケージに関しては、
@vue/test-utils
をはじめ、jestに.vueファイルの処理方法を教えるために必要なvue.jest
や他babel-jest
ts-jest
jest-serializer-vue
などは、
既に@vue/cli-plugin-unit-jest が導入されていれば基本的なところは網羅されているため、特段事情がなければ、まずはこちらで事足りそうです。こちらは
vue-cli
からvue create project
する際に、
Check the features needed for your project
でUnit Testing
にチェック(Pick a unit testing solution: ではJestを選択)で初期導入が可能です。
念の為vue-cli
のバージョンは4系で現在最新の4.5.11
です。既存プロジェクトで初期に導入し損ねていた場合は
vue add unit-jest
で後から追加ができます。次に
jest.config.js
への設定値記述についてです。
公式のガイドの方にjestの設定の必要項目について書かれていますが、
@vue/cli-plugin-unit-jest
導入済みなら、こちらはpresetにtypescript-and-babel
を指定してあげれば良いことがわかりました。
既に書いたUnit Testingオプション付きで vue create project
、
またはvue add unit-jest
した場合こちらは初期で指定されており、同時にテストのサンプルファイルも用意してくれていますね。
このpresetですが、実際に中身を見ると基本的な設定をだいたい網羅してくれていることがわかります。まるっとテストに必要な機構を用意してくれていて、いたれりつくせりといった感じですね。
自分の場合の設定ファイルの中身は必要最低限のところで以下で落ち着きました。jest.config.jsmodule.exports = { preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel', testMatch: ['<rootDir>/src/**/*.spec.ts'], snapshotSerializers: ['jest-serializer-vue'] };テストを書く
実際テストを書いてみるに当たり、どんなプラクティスがあるのかと関連記事を閲覧して周ったりVuetifyのテストファイルを参考にしてみたりしました。
書いてみた結果の簡単な例ですが、
まずは必要なモジュールのインポート。SampleComponent.spec.tsimport { createLocalVue, mount, Wrapper, MountOptions } from '@vue/test-utils'; import SampleComponent from '@/components/SampleComponent.vue';公式にあるようにfactoryメソッドを定義し、 この
mountFunction
をbeforeEach内で作成しておきます。SampleComponent.spec.tsconst localVue = createLocalVue(); let mountFunction: ( options?: MountOptions<SampleComponent> ) => Wrapper<SampleComponent>; beforeEach(() => { mountFunction = (options = {}) => { return mount(SampleComponent, { localVue, ...options }); }; });vuexを注入した場合
SampleComponent.spec.tsimport Vuex from 'vuex'; import { SampleModule } from '@/store/SampleModule'; const localVue = createLocalVue(); localVue.use(Vuex); const createStore = () => { return new Vuex.Store({ modules: { SampleModule } }); }; let mountFunction: ( options?: MountOptions<SampleComponent> ) => Wrapper<SampleComponent>; beforeEach(async () => { mountFunction = (options = {}) => { const store = createStore(); return mount(SampleComponent, { store, localVue, ...options }); }; });vuexの注入に関しては悩みました。
実際書き方のパターンを模索してみる中で、以下のような原則で実装しておくとテストが書きやすそうだと感じました。
- コンポーネントのvuexへの依存を避けて、可能な限りpropsで値を取るべき。
- storeはmodule化しておく。
propsに関してなのですが、値固定ならbeforeEach内でセットしてしまうのもありかと思います。
または初期値を事前に定義しておいてテスト毎にセットするとテストパターンに応じて柔軟性が持てそうです。SampleComponent.spec.tsconst initialProps = { propsData: { id: '12345', name: 'namae' } };使えるAPIの種類や利用例については公式が充実しているので、ここでは簡単な例のテストを2つだけ示したいと思います。
画面上部にバーガーメニューがあり、クリックしたら開くかどうか、また開いた状態のsnapShotを取得するテスト例です。
isOpenMenuと
いうdataを定義し、メニューの開閉状態の制御に使用していることとします。SampleComponent.spec.tstest('スナップショット(メニューを開いた状態)', () => { const wrapper = mountFunction({ ...initialProps, data: () => ({ isOpenMenu: true }) }); expect(wrapper.element).toMatchSnapshot(); }); test('バーガーボタンをクリックするとメニューが開くこと', async () => { const wrapper = mountFunction(initialProps); wrapper.find('.burger_btn').trigger('click'); await flushPrimises(); expect(wrapper.find('.menu > ul').isVisible()).toBe(true); });
flushPromise
でクリック後のdomの更新を待っています。これについては別途パッケージのインストールが必要で、詳しくは公式をご覧ください。
.find().isVisible()
で開いたメニュー内の要素が描画されているかを見ています。以上、簡単ではありますがvueの単一ファイルコンポーネントのテストを書いてみたメモでした。
- 投稿日:2021-01-24T18:44:11+09:00
【Vue.js】静的ホスティングでOGPをURL毎に変えて多言語対応させる
目的
URLに応じて、OGPの内容を切り替えたい。けどSSR(サーバーサイドレンダリング)は使わず静的ホスティングでやりたい。
例)
* hoge.com → 英語のOGPを表示
* hoge.com/ja → 日本語のOGPを表示OGPを切り替えたかったらSSRで行うのが一般的ですが、静的ホスティングでも実現はできなくない。そういう記事がなかったので記録します。シンプルにしたいとか、サーバーの都合とかで静的ホスティングにしたい場合に活用できます。
TL;DR
- MPAでプロジェクトを作成する
- 各htmlファイルに別々にOGPを記入する
- (多言語対応なら)各エントリーポイントで読み込むテンプレートは同一にして、読み込む言語ファイルを動的に変更する
最終構成図
留意点
- 多くのページ(例えばブログの各記事)にOGPを別々に設定する、などには不向きです
- 多言語対応くらいの、運用中に基本増えることがないような量であれば現実的です
参考記事
Vue CLI 3でSPAではなくMPA(複数エントリーポイント)のプロジェクトを作成する
やり方
通常htmlファイルが一つなのがSPAで、Vue.jsの特徴でもありますが、複数のhtmlファイルを用いたMPAも可能です。
OGPを切り替えたいぶんだけhtmlファイルを用意し、それぞれに別々のOGPを記入したMPAプロジェクトを作成することで実現できます。参考記事にMPAのセットアップ方法があり、そこで作成したhtmlファイルに別々のOGPを記入することで、「URL毎にOGPを切り替える」は実現できるので、ここでは割愛させていただきます。
そこから、多言語対応のために、
- テンプレートファイルを一つにする
- URLに応じて言語を切り替える
の2点をするのに必要なセットアップを説明します。
vue-router
複数のURLに対してテンプレートファイルを一つにする場合、vue-routerはhashモードでないといけないです。historyモードだとルーティングがおかしくなってしまいます、、
ファイル構成
私は日本語と英語に対応させたかったので2つ作成しました。中身は後述します。
- 英語用エントリーポイント
- public/index.html
- src/pages/en/main.js
- 日本語用エントリーポイント
- public/ja.html
- src/pages/ja/main.js
- テンプレートファイル(英語日本語共通)
- App.vue
(図ではTypeScriptになっていますがJavaScriptでも同じです)
main.jsの記述
次のように、main.jsを記述します。ポイントは、
* どちらも同じテンプレートApp.vueを読み込む
*Vue.prototype.lang = 'en'
などと言語を指定するen/main.jsimport Vue from 'vue'; import App from '@/App.vue'; Vue.config.productionTip = false; // set language to english Vue.prototype.lang = 'en'; new Vue({ render: h => h(App) }).$mount('#app');ja/main.jsimport Vue from 'vue'; import App from '@/App.vue'; Vue.config.productionTip = false; // set language to japanese Vue.prototype.lang = 'ja'; new Vue({ render: h => h(App) }).$mount('#app');App.vueの記述
プロジェクトをvue-cliで作成したデフォルトのままで大丈夫です。
vue.config.js
次のようにしてエントリーポイント毎にファイルを設定します。
vue.config.jsmodule.exports = { pages: { top: { entry: 'src/pages/en/main.js', // エントリーポイントとなるjs template: 'public/index.html', // テンプレートのHTML filename: 'index.html', // build時に出力されるファイル名 title: 'たいとる' }, ja: { entry: 'src/pages/ja/main.js', template: 'public/ja.html', filename: 'ja.html', title: 'たいとる' } } };OGPの記入
index.htmlには英語のOGPを、ja.htmlには日本語のOGPを記入します。
index.html<!-- OGP --> <meta property="og:url" content="https://hoge.com" /> <meta property="og:type" content="website" /> <meta property="og:title" content="title" /> <meta property="og:description" content="description" /> <meta property="og:site_name" content="title" /> <meta property="og:image" content="https://hoge.com/hoge.png" />動作確認
ここまででのセットアップで、2つのURL
hoge.com/
とhoge.com/ja
どちらもApp.vueのページが表示されるようになったはずです。そうなっていれば、最終構成図の矢印で示された接続は完成しています。twitterシェアデバッガーなどにURLを入力したら、2つのURLで別々のOGPが表示されるはずです。言語の動的な切り替え
main.js で
Vue.prototype.lang='en'
などと設定したことで、テンプレート内のどこでも、Vue.prototype.lang
とすることで今どの言語のページが表示されているかが読み込めるようになります。言語別に表現をjsonファイルなどで用意しておき、Vue.prototype.lang
によって読み込みを変えることで多言語対応が実現できます。App.vue内でURLを読み取って言語判定することもできますが、main.js内で行うとマウントの前に言語判定できて、またコードもきれいになると思います。
(例)
langs.json{ "title": { "en": "TITLE", "ja": "たいとる } }App.vue<h1>{{ title }}</h1> <script> created() { // langsJsonにlangs.jsonを読み込んでおく this.title = langJson.title[Vue.prototype.lang] } </script>具体例
こちらの仕組みで多言語対応を行ったサイトがこちらになります。
ユーザーネームチェッカー課題
- hashモードを利用しないといけないためURLがきれいじゃない、、
- 投稿日:2021-01-24T17:49:14+09:00
【Vue.js】classをデータにバインディングする
Vue.jsでは、v-bindでclass属性を設定した際に、下記の様なオブジェクトを渡すことにより、class属性の値を適用、不適用を切り替えることができる。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <style> .red { background-color: red; } .blue { background-color: blue; } </style> <div id="app"> <h1 :class="{red:true, blue:false}">Hello</h1> </div> <script> new Vue({ el: '#app', }) </script><script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <style> .red { background-color: red; } .blue { background-color: blue; } </style> <div id="app"> <h1 :class="{red:false, blue:true}">Hello</h1> </div> <script> new Vue({ el: '#app', }) </script>v-bindされたclass属性の値にオブジェクトを渡して動的に切り替える。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <style> .red { background-color: red; } .blue { background-color: blue; } </style> <div id="app"> <h1 :class="classChange">Hello</h1> <button @click="change">切り替え</button> </div> <script> new Vue({ el: '#app', data: { isActive: true }, methods: { change: function () { this.isActive = !this.isActive }, }, computed: { classChange: function () { return { red: this.isActive, blue: !this.isActive, } } } }) </script>データの流れ
- 投稿日:2021-01-24T17:48:37+09:00
【Vue.js】computedの記法について(methodsとの違い)
下記のようなクリックの回数によって、動的に表示を変えるプログラムがあるとする。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <button @click="counter">カウンター</button> <div>{{number >3 ? '3より上':'3より下'}}</div> </div> <script> new Vue({ el: '#app', data: { number: 0 }, methods: { counter: function () { this.number += 1 } } }) </script>conputedオブジェクトを用いて実装する方法
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <button @click="counter">カウンター</button> <div>{{check}}</div> </div> <script> new Vue({ el: '#app', data: { number: 0 }, methods: { counter: function () { this.number += 1 } }, computed: { check: function () { return this.number > 3 ? '3より上' : '3より下' } } }) </script>次にmethodsを用いて同じ実装をしてみる。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <button @click="counter">カウンター</button> <div>{{ check() }}</div> </div> <script> new Vue({ el: '#app', data: { number: 0 }, methods: { counter: function () { this.number += 1 }, check: function () { return this.number > 3 ? '3より上' : '3より下' } }, }) </script>結論として、同じ挙動をするが、関数の呼び出すタイミングが異なる。
computedとmethodsの違い
methodsを定義し、{{ }}で呼び出した場合、画面が再描写されたタイミングで実行される。
つまり、全く異なる別の処理で、画面が再描写された際、check()関数が呼び出されてしまう。
これは、不必要で無駄な動作である。computedで定義した場合は、定義した関数内部の値に変化があった場合に、呼び出される。
よって、他の処理に影響されない。検証
下記の様に、フォームと出力部分を実装する。
methodsを用いて実装。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <button @click="counter">カウンター</button> <div>{{ check() }}</div> <p>{{test}}</p> <input v-model="test"> </div> <script> new Vue({ el: '#app', data: { number: 0, test: "test" }, methods: { counter: function () { this.number += 1 }, check: function () { console.log('run'); return this.number > 3 ? '3より上' : '3より下' } }, }) </script>methodsで実装した場合、フォームの値が変更され再描写されるたびに、コンソールにrunが出力されている。
つまり、関係のない処理にもかかわらず、checkプロパティに定義した関数が呼び出されてしまっている。computedを用いて実装
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <button @click="counter">カウンター</button> <div>{{ check }}</div> <p>{{test}}</p> <input v-model="test"> </div> <script> new Vue({ el: '#app', data: { number: 0, test: "test" }, methods: { counter: function () { this.number += 1 }, }, computed: { check: function () { console.log('run'); return this.number > 3 ? '3より上' : '3より下'; } } }) </script>フォームの値を変更しても、runは呼び出されることはない。カウンターボタンを押した時のみ、runは出力される。
- 投稿日:2021-01-24T13:57:54+09:00
【Vue.js】v-modelの記法について
v-modelを用いることにより双方向データバインディングを作成する。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <input type="text" v-model="message"> <h1>{{message}}</h1> </div> <script> new Vue({ el: '#app', data: { message: 'こんにちは' } }) </script>v-modelで指定した属性の値が変更したら、一致するdataの値も同期される。
- 投稿日:2021-01-24T13:27:41+09:00
【Vue.js】v-onの記法について
v-onでDOMにイベントを設定する。
クリックイベントの例
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <button v-on:click="display">表示</button> </div> <script> new Vue({ el: '#app', methods:{ display:function(){ console.log("hello") } } }) </script>v-on:イベント="メソッド名" で イベントを設定できる。
イベントの内容は、methodsオブジェクトで定義。v-onディレクティブに引数を持たせる。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <p>{{number}} </p> <button v-on:click="countUp(3)">カウントアップ</button> </div> <script> new Vue({ el: '#app', data: { number: 0, x: 0, y: 0 }, methods: { countUp: function (times) { this.number += 1 * times }, } }) </script>イベントオブジェクトを利用する。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <p v-on:mousemove="getPosition($evnet)">マウス載せて</p> <p>x: {{x}}, y:{{y}}</p> </div> <script> new Vue({ el: '#app', data: { x: 0, y: 0 }, methods: { getPosition: function (event) { this.x = event.clientX; this.y = event.clientY; } } }) </script>引数に \$event をとることで、イベントオブジェクトを取得することができる。引数が、イベントオブジェクトのみの場合は、($event)の記載自体を省略できる。
イベント修飾子
v-onメソッドに 修飾子を与えることによって、イベントの挙動を制御する
例 stop修飾子でイベントの伝播を止める。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <p v-on:mousemove="position($event)">マウス <!--================stop修飾子を設定====================--> <span v-on:mousemove.stop>【ここは反応しないで】</span> <!--==================================================--> 載せて </p> <p>x: {{x}}, y:{{y}}</p> </div> <script> new Vue({ el: '#app', data: { number: 0, x: 0, y: 0 }, methods: { position: function (event) { this.x = event.clientX; this.y = event.clientY; } } }) </script>例 prevent修飾子でデフォルトの挙動を防ぐ
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <a v-on:click.prevent href="https://google.com">google</a> </div>key修飾子
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <input type="text" v-on:keyup="myAlert"> </div> <script> new Vue({ el: '#app', methods: { myAlert() { alert('キーボードが操作されました'); } } }) </script>enterを押下した時のみイベントを発生。
key修飾子[enter]を設定。<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <input type="text" v-on:keyup.enter="myAlert"> </div> <script> new Vue({ el: '#app', methods: { myAlert() { alert('エンターがおされました'); } } }) </script>動的にイベントを設定する。
[]で設定する。
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.js"></script> <div id="app"> <input type="text" v-on:[event]="myAlert"> </div> <script> new Vue({ el: '#app', data: { event: 'keyup' }, methods: { myAlert() { alert('入力されました'); } } }) </script>省略記法
v-on: は @で省略できる。
<input type="text" v-on:[event]="myAlert"> <input type="text" @[event]="myAlert">
- 投稿日:2021-01-24T01:26:10+09:00
laravelで外部APIを取得し、Ajaxで取得
外部APIを使うことになったためメモ。
Laravelではguzzleというライブラリを使って外部APIを取得。terminalcomposer require guzzlehttp/guzzleApiController<?php namespace App\Http\Controllers; use GuzzleHttp\Client;//忘れず class PostController extends Controller { public function index() { $api_key = "hogehogehogehogehofe";//ここにAPI_KEY $url = "https://hoge.com/api/" . $api_key;//url次第 $method = "GET"; //接続 $client = new Client(); $response = $client->request($method, $url); $posts = $response->getBody(); $posts = json_decode($posts, true);//jsonに変換 return view('index', ['posts' => $posts]); } }api.phpRoute::get('/index', 'ApiController@index');Index.vue<script> export default { created(){ axios.get('/api/index') .then((response) => { console.log(response.data); }) } } </script>キーがない場合はそのままaxiosで取得できるが、キーがある場合はエラーになるためguzzleが必要そう。
- 投稿日:2021-01-24T00:10:05+09:00
【Vue.js】v-onディレクティブの省略記法
indx.html<div id="app"> <button v-on:click="clickHandler"> click1 </button> <button @click="clickHandler"> click2 </button> </div> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>main.jsvar app = new Vue({ el: "#app", methods: { clickHandler: function() { alert('がんばれ') } } })v-on:clickでも@clickでもどちらでも大丈夫。