- 投稿日:2020-10-27T23:41:49+09:00
Vue.jsで、いろんなやり方の画像プレビュー方法
ref属性から画像を取得して、表示する
// refからアップロード input(type="file", ref="file", @change="leftSetImage") v-img(:src="image.source")// refから一時的なURLに変換して取得 leftSetImage() { const files = this.$refs.file; const fileImg = files.files[0]; if (fileImg.type.startsWith("image/")) { this.image.source = window.URL.createObjectURL(fileImg); } },イベントオブジェクトから画像を取得して、表示する
一時的なURLでプレビュー表示
// イベントオブジェクトからアップロード input( type="file", @change="setImageLeft($event)" ) v-img(:src="office.left_image.image", type="submit")// イベントオブジェクトから、一時的URLに変換して取得 setImageLeft(e) { const file = (e.target.files || e.dataTransfer)[0]; this.LeftUploadedImage = file; if (file.type.startsWith("image/")) { this.office.left_image.image = window.URL.createObjectURL(file); } },base64に変換して、プレビュー表示
今までのやり方だと、ブラウザを放置して更新したり、そのデータを画像を保存したりした時の再表示の動作ができなくなるということが起きる。
base64に変換する場合は、そのような事象は起きない。
// イベントオブジェクトからアップロード input( type="file", @change="setImageRight($event)" ) v-img(:src="office.right_image.image", type="submit")// イベントオブジェクトから、base64に変換して取得 setImageRight(e) { const file = e.target.files[0] || e.dataTransfer.files; const reader = new FileReader(); reader.onload = (e) => { this.office.right_image.image = e.target.result; }; reader.readAsDataURL(file); },
- 投稿日:2020-10-27T23:41:49+09:00
Vue.jsならではの、いろんなやり方の画像プレビュー方法
ref属性から画像を取得して、表示する
// refからアップロード input(type="file", ref="file", @change="leftSetImage") v-img(:src="image.source")// refから一時的なURLに変換して取得 leftSetImage() { const files = this.$refs.file; const fileImg = files.files[0]; if (fileImg.type.startsWith("image/")) { this.image.source = window.URL.createObjectURL(fileImg); } },イベントオブジェクトから画像を取得して、表示する
一時的なURLでプレビュー表示
// イベントオブジェクトからアップロード input( type="file", @change="setImageLeft($event)" ) v-img(:src="office.left_image.image", type="submit")// イベントオブジェクトから、一時的URLに変換して取得 setImageLeft(e) { const file = (e.target.files || e.dataTransfer)[0]; this.LeftUploadedImage = file; if (file.type.startsWith("image/")) { this.office.left_image.image = window.URL.createObjectURL(file); } },base64に変換して、プレビュー表示
今までのやり方だと、ブラウザを放置して更新したり、そのデータを画像を保存したりした時の再表示の動作ができなくなるということが起きる。
base64に変換する場合は、そのような事象は起きない。
// イベントオブジェクトからアップロード input( type="file", @change="setImageRight($event)" ) v-img(:src="office.right_image.image", type="submit")// イベントオブジェクトから、base64に変換して取得 setImageRight(e) { const file = e.target.files[0] || e.dataTransfer.files; const reader = new FileReader(); reader.onload = (e) => { this.office.right_image.image = e.target.result; }; reader.readAsDataURL(file); },
- 投稿日:2020-10-27T21:17:36+09:00
Vue 3.0にてcomposition apiを使ったcomposableパターンでテストを書く
昔Advent Calendarのために書いた記事をvue-next版に書き直しました。
https://qiita.com/jiko21/items/12c79b7b831276e9a088TL;DR
- composition-apiを使うことでロジックをUIから分離してテストできる
- ただし、少し考えてテストを書く必要あり
はじめに
9/18にVue 3.0がリリースされました!
これにより、composition-api
がプラグイン無しで利用できるようになりました。
composition-api
の詳しい使い方はここでは省略しますが、これにより、コンポーネントからロジックを別ファイルへと分離することもできます。今回は、コンポーネントからロジックを分離した際のテストの書き方を説明します。
コードはすべてこちらにあります。
ロジックとUIの分離
例えば、TODOアプリの場合は、TODOリストに追加、削除する処理や、TODOリストのデータなどをComponentに直接記述するのではなく、
あくまで別のファイルに記述し、Component側でそれらを呼び出す、といったことがComposition-apiでは可能となります。@/composable/todo.tsimport { computed, reactive } from 'vue'; const useTodo = () => { const todo = reactive({ todos: [] as string[], length: computed(() => todo.todos.length), }) as any; const addTodo = (item: string) => { todo.todos.push(item); }; const deleteTodo = (index: number) => { todo.todos.splice(index, 1); }; return { todo, addTodo, deleteTodo, }; }; export default useTodo;Todo.vue<template> <div class="count"> <input id="todo-input" v-model="text"/> <button class="add-btn" @click="onSubmit">追加</button> <ul> <li v-for="(task, i) in todo.todos" :key="i"> <p>{{task}}</p> <button class="delete-btn" @click="deleteTodo(i)">Delete</button> </li> </ul> </div> </template> <script lang="ts"> import { defineComponent, ref } from 'vue'; import useTodo from '@/composable/todo'; export default defineComponent({ name: 'Todo', setup() { const text = ref(''); const { todo, addTodo, deleteTodo } = useTodo(); // 外部ファイルに書いたロジックを読み込む ... }, }); </script> <style scoped> </style>テストの実装
このようにロジック(composable)とUIを分離したので次はテストの実装方針を説明します。
まずは、ロジック側からですが、実際にモジュールとして正しく動くか
を検証します。(当たり前っちゃ当たり前かもですが)
そして、UI側ではボタンタップ時にロジックをcallできているか
を検証します。
ロジック側のテスト
サービス層やutilで書いたテストコードと同様に、実際に呼び出しを行い検証を行います。
(もしcomosableに何らかの依存関係がある場合はそれをmockしてください)todo.spec.tsimport useTodo from '@/composable/todo'; describe('todo.spec.ts', () => { it('addTodo should work properly', () => { const { todo, addTodo, deleteTodo } = useTodo(); addTodo('hogehoge'); expect(todo.todos).toEqual(['hogehoge']); }); ... });UI側のテスト
ロジック(composable)をモックしてやり、それが呼ばれたかを検証します。
compsable内にstateがある場合はそれが表示されるかも確認しておきましょう。Todo.spec.tsimport { mount, VueWrapper } from '@vue/test-utils'; import Todo from '@/components/Todo.vue'; import * as composable from '@/composable/todo'; describe('Todo.vue', () => { let wrapper: VueWrapper<any>; let addTodoMock: jest.Mock; let deleteTodoMock: jest.Mock; beforeEach(() => { jest.mock('@/composable/todo'); addTodoMock = jest.fn(); deleteTodoMock = jest.fn(); const TODOS = [ 'アドベントカレンダー', '修論', '筋トレ', ]; jest.spyOn(composable, 'default').mockReturnValue({ // Reactiveはデータ構造そのままでOK! todo: { todos: TODOS, length: () => TODOS.length, }, addTodo: addTodoMock, deleteTodo: deleteTodoMock, }); wrapper = mount(Todo); }); it('correctly renders initial html', () => { expect(wrapper.html()).toMatchSnapshot(); }); it('correctly call deleteTodo when `Delete` button is clicked', () => { const INDEX = 1; wrapper.findAll('.delete-btn')[INDEX].trigger('click'); expect(deleteTodoMock).toHaveBeenCalledWith(INDEX); }); ... });ここで重要なのは、Reactiveのモック方法です。
データ構造そのままのObjectをmockしてやればできます。これはRefも同様で、
const countValue = ref(0); countValue.value;のように使用するので
jest.spyOn(composable, 'default').mockReturnValue({ countValue: { value: 0, }, increment: incrementMock, decrement: decrementMock, });と書いてしまいたくなりますが
count.spec.tsjest.spyOn(composable, 'default').mockReturnValue({ countValue: 0 as any, increment: incrementMock, decrement: decrementMock, }); wrapper = mount(Count);のように書いてやる必要があります。
最後に
このようにすれば、composableとしてロジックを分離した場合でもテストがかけます。
ただし、vue-test-utilsが9/28時点でまだ2.0.0-beta.5
なのでまだまだ安定しないかもしれません。
- 投稿日:2020-10-27T20:50:47+09:00
vue.jsを初めて学ぶ ② [テンプレート構文]
前回までの記事
↓
① hello world目次
14. classをデータにバインディングする方法(オブジェクトと配列)
テンプレート構文とは
index.jsnew Vue({ el: '#app', data: { message: 'Hello World' }
- Vue.jsが上記テンプレート構文をロード
- Vue.jsがhtmlを出力
- ブラウザがhtmlをロード
- 私達がブラウザで確認できる
new vueテンプレート解説
index.html<div id = "app"> <!-- [1]文字列を出力 --> <p>{{message}}</p> <!-- [2]和を出力 --> <p>{{number + 3}}</p> <!-- [3]三項演算子 --> <p>{{ok ? 'yes' : 'no'}}</p> <!-- [4]メソッドを出力 --> <p>{{ sayHi() }}</p> </div>index.jsnew Vue({ el: '#app', data: { <!-- [1]文字列を出力 --> message: 'Hello World' <!-- [2]和を出力 --> number: 3 <!-- [3]三項演算子 --> ok: true } <!-- [4]メソッドを出力 --> methods: { sayHi: function() { return: 'Hi'; } } })三項演算子とは?
唯一の、3 つのオペランドをとる演算子です。
1. 条件に続いて疑問符 (?)
2. 条件が真値であった場合に実行する式
3. コロン (:) が続く
4. 条件がfalseであった場合に実行する式
が最後に来ます注意: javascriptが出力されるのは、単一の式のみ
プロパティの種類
1. el
マウント先の要素を指定するブロック
2. data
変数を保持するブロック
3. method
関数を保持するブロック
4. computed
関数を保持するブロック
特徴として、returnを使用して返り値が必要。
そして、依存関係に基づきキャッシュされる。5. watch
関数を保持するブロック
特徴として、returnを使用しないため非同期処理を記述できる
ディレクティブの種類
- ディレクティブとは、v-から始まるVue.jsの特別な属性
- 属性とは、htmlではhref,class,idといった要素 HTMLにおける、属性とは
1. v-text
2. v-once
3. v-html (xss脆弱性に注意)
対応:信頼のあるコンテンツしか置かないようにする。
4. v-bind
1. href
2. class
3. id
属性を与える事ができる5. v-on
使用頻度が多い、DOMイベントを引数として発火する。
様々な関数を、起動させることが可能。6. v-model
双方向データバインディングが可能
v-bindとは
属性を与える事ができる
index.html<div id = "app"> <a href="url">Google</a> </div>index.jsnew Vue ({ data: { url: 'https://google.com', } })Vueテンプレートで、url属性を与えようとする。。。
404エラーとなってしまう。
v-bind使用例
index.html<div id = "app"> <a v-bind:href="url">Google</a> </div>index.jsnew Vue ({ data: { url: 'https://google.com', } })v-bind省略記法
index.html<div id = "app"> <a :href="url">Google</a> </div>index.jsnew Vue ({ data: { url: 'https://google.com', } })v-onディレクティブ省略記法
- v-onを省略する。index.html<p>現在{{number}}回クリックされています</p> <button @click="countUp">countUp</button>省略が可能な記法(:や@)は、プロジェクト内で統一する。
v-bindを オブジェクト化 して使用する。
- hrefリンク
- class
- id
複数の属性を与えたい時、 オブジェクト化 が最適方法
index.html<div id = "app"> <a v-bind="objectGoogle">Google</a> </div>index.jsnew Vue ({ data: { objectGoogle: { href: 'https://google.com', class: 'sample', id: 31 } } })chromeブラウザの「検証」より、v-bindで属性が与えられているのが確認できる
v-onとは
DOMイベントが発火するタイミングで、実装した関数を実行できる
ボタンクリックで数字がカウントされる処理を実装してみる。
v-on:に指定するのは、clickイベント
index.html<div id = "app"> <p>現在{{number}}回クリックされている</p> <button v-on:click="countUp">カウントアップ</button> </div>index.jsnew Vue ({ el: '#app', data: { number: 0 }, methods: { countUp: function() { this.number += 1 } } })
- clickイベントが発火する
- countUp関数が起動 ( 変数numberに、+1 )
- {{ number }} に反映
マウスをかざした場所の、XY軸を表示する
v-on:に指定するのは、mousemoveイベント
index.html<div id = "app"> <p v-on:mousemove="changeMousePosition">マウスを載せてください</p> <p>X:{{x}},Y{{y}}</p> </div>index.jsnew Vue ({ el: '#app', data: { x:0, y:0, }, methods: { changeMousePosition: function(event) { this.x = event.clientX; this.y = event.clientY; } } })DOMイベントの情報を拾うには、ルールがある
1. 引数にはeventを文字として使用。他でも格納できるがよっぽど理由がなければ変更しない。
2. 格納したeventから読み取る事。
イベント修飾子とは
- 従来のstopPropagationを、stop で実装可能
- 従来のpreventDefaultを、prevent で実装可能
処理をSTOPさせる
従来のstopPropagation
index.html<div id = "app"> <p v-on:mousemove="changeMousePosition">マウスをかざしてください <span v-on:mousemove="noEvent">マウスをかざしても反応させない。</span> </p> <p>X:{{x}},Y{{y}}</p> </div>index.jsnew Vue ({ el: '#app', data: { x:0, y:0, }, methods: { changeMousePosition: function(event) { this.x = event.clientX; this.y = event.clientY; }, noEvent: function(event) { event.stopPropagation() } })
- spanタグにnoEvent関数を追加
- noEvent関数に、stopPropagationという処理をstopさせる命令を追加
Vue.jsのすごい所
- v-on:mousemove.stopを与えるのみ!
index.html<div id = "app"> <p v-on:mousemove="changeMousePosition">マウスをかざしてください <span v-on:mousemove.stop>マウスをかざしても反応させない。</span> </p> <p>X{{x}},Y{{y}}</p> </div>index.jsnew Vue ({ el: '#app', data: { x:0, y:0, }, methods: { changeMousePosition: function(event) { this.x = event.clientX; this.y = event.clientY; } })処理をpreventさせる
- preventとは、html側のデフォルト設定までも起動させない
リンクタグをクリック、飛ばないように設定。
従来のpreventDefault
index.html<div id = "app"> <a v-on:click="noEvent" href="https://google.com">google.com</a> </div>index.jsnew Vue ({ el: '#app', methods: { noEvent: function(event) { event.preventDefault(); } })Vue.jsのすごい所
- html側に、v-on:mousemov.preventを与えるのみ!
index.html<div id = "app"> <a v-on:click.prevent href="https://google.com">google.com</a> </div>index.jsnew Vue ({ el: '#app', })stopとpreventを、つなげる事も可能
v-on:mousemove.stop.prevent
キー修飾子とは
キーボード入力イベントに対する修飾子
1. エンターキーを入力時点で動作させる。.enter
2. スペースキー入力時点で動作させる。.space
index.html<div id = "app"> <input type="text" v-on:keyup.enter="myAlert"> </div>index.jsnew Vue ({ el: '#app', methods: { myAlert() { alert('アラート'); } } })つなげる事も可能
v-on:keyup.enter.space
v-modelとは
双方向データバインディング
1. inputにテキストを入力
2. 変数messageの内容が置き換わる。
3. data内のmessageが置き換わる。
4. h1タグのmessageも変更される。index.html<div id = "app"> <input type="text" v-model="message"> <h1>{{message}}</h1> </div>index.jsnew Vue ({ el: '#app', data: { message: 'こんにちは', } })
computedプロパティとは
- dataプロパティでは、動的なデータは扱えない。(プレーンなテキストのみ)
- computedプロパティを使用する
index.html<div id = "app"> <p>{{ counter }}</p> <button v-on:click="countUp">+1</button> <p>{{ lessThanThree }}</p> </div>index.jsnew Vue ({ el: '#app', data: { counter: 0 }, methods: { countUp: function() { this.counter += 1 } }, computed: { lessThanThree: function() { return this.counter > 3 ? '3より上' : '3以下' } } })computedプロパティの関数、lessThanThreeには
- 引数が必要ではない
- returnで値を返す必要がある
関数lessThanThreeは、
methodsプロパティでは実現できないのか?結論:
できるけど、、
最適化するならComputedメソッド一択!
- computedプロパティは依存関係に基づきキャッシュされる特徴がある。
- methodsプロパティは、全く関係のないメソッドの更新にも反応する。
- 大規模になると、methodsプロパティで記述した関数が無駄に実行されてしまう。
index.html<div id = "app"> <p>{{ counter }}</p> <button v-on:click="countUp">+1</button> <p>{{ lessThanThreeMethod() }}</p> <p>{{ lessThanThreeComputed }}</p> </div>index.jsnew Vue ({ el: '#app', data: { counter: 0 }, methods: { countUp: function() { this.counter += 1 }, lessThanThreeMethod: function() { return this.counter > 3 ? '3より上' : '3以下' } }, computed: { lessThanThreeComputed: function() { return this.counter > 3 ? '3より上' : '3以下' } } })
watchプロパティとは
- データが変わったとき、非同期処理 が実行
- 非同期処理の中では、thisが使用不可
- computedプロパティでは、returnが必要(同期処理)になる。
- 非同期処理したい場合はwatchプロパティが最適。
3秒でカウントの値(counter)が0にリセットされる非同期処理
index.html<div id = "app"> <p>{{ counter }}</p> <button v-on:click="countUp">+1</button> <p>{{ lessThanThreeComputed }}</p> </div>index.jsnew Vue ({ el: '#app', data: { counter: 0 }, methods: { countUp: function() { this.counter += 1 } }, computed: { lessThanThreeComputed: function() { return this.counter > 3 ? '3より上' : '3以下' } }, watch: { counter: function() { var vm = this; setTimeout(function(){ vm.counter = 0 }, 3000) } } })
thisを理解する。
変数messageを、 関数sayHi内で 呼び出してみる。
index.jsnew Vue({ el: '#app', data: { message: 'Hello,World!!' }, methods: { sayHi: function() { <!--これはエラーになる--> return message; <!--正しくは、thisが必要--> return this.message; } } })index.html<div id = "app"> <p>{{ sayHi() }}</p> </div>thisとは、
Vue.jsのインスタンスにアクセスするために必要なプロキシ
括弧の使い方
computedプロパティでは、メソッド名のみを{{二重カッコ内}}に記載
computedプロパティに()を足すと、エラーとなり表示されなくなる。methodsプロパティでは、メソッド名+()を{{二重カッコ内}}に記載
methodプロパティから()を除くと、function () { [native code] }
と表示される。v-ディレクティブの引数としてメソッド名を指定する場合
()は付与してもしなくてもどちらでもOK。
引数には、javascriptコードを書いてもOK。
classをデータにバインディングする方法
{ オブジェクト }で記載
- ハイフンの記載がオブジェクト内にある場合、'bg-color'とシングルクォーテーションで括る必要あり。
index.html<div id = "app"> <h1 :class="{red: true, 'bg-blue': false}">はろう</h1> </div>index.jsnew Vue ({ el: '#app' })sample.css.red { color: red; } .bg-blue { background-color: blue; }ボタンを設置して、クラスの付与を分岐する。
index.html<div id = "app"> <h1 :class="classObject">はろう</h1> <button @click="isActive = !isActive">切り替えボタン</button> </div>index.jsnew Vue ({ el: '#app', data: { isActive: true }, computed: { classObject: function() { return { red: this.isActive, 'bg-blue': !this.isActive } } } })sample.css.red { color: red; } .bg-blue { background-color: blue; }[ 配列 ] で記載
index.html<div id = "app"> <h1 :class="[color, bg]">はろう</h1> </div>index.jsnew Vue ({ el: '#app', data: { color: 'red', bg: 'bg-blue', } })text.sample.css.red { color: red; } .bg-blue { background-color: blue; }
- 投稿日:2020-10-27T20:24:35+09:00
【vue】methodsの省略表記と注意点(アロー関数は使えない)
【vue】methodsの省略表記と注意点
・OK:functionを使わない省略表記を使うのが一般的。
・NG:アロー関数を使う省略表記methods:{ メソッド名(){ 処理 } }メソッド名の後ろにカッコを直接つける。
NG事例(アロー関数)
アロー関数を使った場合、処理の中でプロパティを呼び出すことができない。
NG事例(アロー関数)export default { data(){ return{ text: "ボタン" } }, methods:{ メソッド名:()=>{ console.log(this.text) } } }エラー内容[Vue warn]: Error in v-on handler: "TypeError: Cannot read property 'text' of undefined"textを正しく設定してるのに、呼び出すことができない。
参考(3つの処理)
- 省略形
- function使用(冗長表記)
- アロー関数(NG事例)
Methods.vue<template> <div> <v-btn @click="showConsole1"> {{text}}1 </v-btn> <br> <v-btn @click="showConsole2"> {{text}}2 </v-btn> <br> <v-btn @click="showConsole3"> {{text}}3 </v-btn> </div> </template> <script> export default { data(){ return{ text: "ボタン" } }, methods:{ showConsole1(){ console.log("showConsole1:", this.text) }, showConsole2:function(){ console.log("showConsole2:", this.text) }, showConsole3:()=>{ console.log("showConsole3:", this.text) } } } </script>▼ブラウザの表記
▼実行結果
ボタン1、2は正常に作動するが、アロー関数を用いた3はエラーとなる。
- 投稿日:2020-10-27T19:55:34+09:00
Vueのvalueプロパティ。親で定義してないのに子のpropsにvalueがある理由。
Vueのvalueプロパティ。親で定義してないのに子のpropsにvalueがある理由。
子のpropsにvalueがあるのに、親のどこを探してもvalueがなくデータ元が不明すぎて散々悩んだので、その解決法について。
>propsの役割
親から子へのデータ受け渡しで、子のpropsプロパティに受け取る変数を定義する。結論
v-modelで渡すデータの初期設定がvalueだった。。(text要素の場合)
親<TextFiled v-model="rows" />子(TextField)export default { props:{ value={}, }親の
v-model="rows"
でrowsという変数をTextFieldに渡している。このとき、データはvalueに格納されている。
つまり、親から子へのデータ受け渡しだけに着目すると、以下2つは同じになる。
v-model="rows"
⇅
:value="rows"
v-modelのデフォルトの変数
要素 初期プロパティ名 初期イベント名 text要素/textarea要素 value input チェックボックス/ラジオボタン checked change セレクトボックス value change
- 投稿日:2020-10-27T18:39:05+09:00
Vuejsでフラッシュカードを作りましょう(音声でもコントロール可能)
やりたいこと
今回、Vuejsを勉強のため、簡単なフラッシュカードのウェッブアプリを作成します。
フラッシュカードは質問と答えの面が2つあって、その二つ面を交換すために、「フリップ」があります。実装
データを準備する
まず、HTMLとVuejsの要素を作って、連携しましょう。
<div id="flashCardApp">const flashCardApp = new Vue({ el: '#flashCardApp', data: {}, methods: {}dataのほうはcardsとshowedCardがあります。cardsは全部アプリのカードです。showedCardは表示されるカードになります。で、アプリ起動する時、表示されるカードは最初のカードになります。
cards = [{カード1の情報}, {カード1の情報}, {カード3の情報},...]; data: { cards: cards, showedCard: cards[0] }カード情報は:
id: カードのID(例: 0, 1, 2) frontText: 質問の言葉(例: BANK, CHURCH) backText: 言葉の意味(例:銀行, 教会) colorClass: カードの色(責任) flipped: どんな面が表示されるか(例: true, false)です。
前と次のカードのボータン
表示されるカードは一つだけなので、全部のカードを見えるように、カード間に移動するボータンは必要になります。また、最後の次のカードは最初のカードです。逆に最初のカードの前は最後のカードになります。
JS側
methods: { previosCard: function() { let showedId = this.showedCard.id - 1; if(showedId < 0){ showedId = cards.length - 1; } this.showedCard = cards[showedId]; }, nextCard: function() { let showedId = this.showedCard.id + 1; if(showedId >= cards.length){ showedId = 0; } this.showedCard = cards[showedId] } }HTML側
<button v-on:click="previosCard" class="button-right">前</button></div> <button v-on:click="nextCard" class="button-left">次</button>カードのフリップのボータン
ユーザはカードのフロント面とバック面を交換するボータンを押すと、現在表示されるカードのflippedの状態を変更して、Vuejsはその状態の変更に対して自動的にカードのフロントとバック面を交換してもらいます。
js側methods: { flipCard: function() { this.showedCard.flipped = !this.showedCard.flipped; } }html側
<transition name="flip"> <div v-show="!showedCard.flipped" @click="flipCard" class="card-text card-front-text"> <div class="pretext"> 単語 </div> <h2> {{showedCard.frontText}} </h2> <button class="button-white button-clear button-large"> フリップ <i class="fas fa-sync-alt"></i> </button> </div> </transition> <transition name="flip"> <div v-show="showedCard.flipped" @click="flipCard" class="card-text card-back-text"> <div class="pretext"> 意味 </div> <h2> {{showedCard.backText}} </h2> <button class="button-white button-clear button-large"> フリップ <i class="fas fa-sync-alt"></i> </button> </div> </transition>音声でフラッシュカードをコントロールしましょう。
annyangは、ユーザが音声コマンドでサイトを制御できるようにする小さなjavascriptライブラリです。
annyangの使い方は本当に簡単です。if (annyang) { // コマンドを定義する const commands = { '次': () => { flashCardApp.nextCard(); }, 'フリップ': () => { flashCardApp.flipCard(); }, '前': () => { flashCardApp.previosCard(); }, }; //入力の言語を日本語に変更する annyang.setLanguage("ja"); // ↑の定義したコマンドを追加しする annyang.addCommands(commands); // マイクロからの声が聞こえ始めます annyang.start(); }それで、「次」と「前」と「フリップ」のコマンドを言ってもらうと、フラッシュカードをコントロールできます。ただ、音声認識はクラウドで処理するので、少しだけディレイ感じがあります。
以上です。
結果と参考
デモ: https://codepen.io/tardigrades2/full/oNLZovL
コード: https://codepen.io/tardigrades2/pen/oNLZovL)
参考したコード: https://codepen.io/mgnmrt/pen/pKZVYg
- 投稿日:2020-10-27T18:18:52+09:00
Uncaught ReferenceError: Vue is not definedが起こった話
はじめに
Vue.jsをCDN読み込みで簡単なミニアプリを使う際に起こったエラーです。
備忘録のため残しておきます。初学者のため間違えがあればご指摘下さい!Uncaught ReferenceError: Vue is not defined
vue.jsの動作確認をするため以下のコードを記述したところvue.jsが読み込まれませんでした。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="vue.css"> <title>Document</title> </head> <body> <div id="app"> <p>現在{{ number }}回クリックされています</p> <button>カウントアップ</button> </div> <script src="vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> </body> </html>//vue.js new Vue({ el: '#app', data: { number: 0 } })解決策は簡単。
下から3行目のVueを使うためのCDNをjsが読み込まれるより上に記述するだけです。
headタグの中に入れるかbodyタグの一番上に記述しましょう。<!-- 修正後 --> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <link rel="stylesheet" href="vue.css"> <title>Document</title> </head> <body> <div id="app"> <p>現在{{ number }}回クリックされています</p> <button>カウントアップ</button> </div> <script src="vue.js"></script> </body> </html>最後に
Vue.jsの超初心者の方の助けになれば幸いです。
- 投稿日:2020-10-27T17:23:55+09:00
branchを指定してcloneする方法
背景
YouTubeで、Vuetifyのチュートリアルを学習しようと思った。動画投稿者が、githubにサンプルコードを載せてくれていた。それは、各lessonごとにbranchが切られていたので、ただgit cloneするだけだと、目的のファイルが入手できず、branchごとにcloneする必要があった。
これ→https://github.com/iamshaunjp/vuetify-playlist
方法
$ git clone -b ブランチ名 cloneのhttpsつまり
$ git clone -b git clone -b lesson-2 https://github.com/iamshaunjp/vuetify-playlist.gitです。
- 投稿日:2020-10-27T14:53:09+09:00
watchプロパティで入力内容を即時反映する方法。子から親へのデータ受け渡し
watchプロパティで入力内容を即時反映する方法。子から親へのデータ受け渡し
watchプロパティを使うと画面上の変更内容をリアルタイムで検知し、設定した処理を実行することができる。
watchプロパティを使う手順
①変数を定義する ↓ ②変数に対してwatachプロパティを設定するwatchプロパティは変数の変更を感知するため、変更内容を監視する変数を指定する。
※注意点
・watchプロパティの中で監視する変数を指定。
┗ 変数名:function(){処理}・変数内のネストしたデータを監視する場合は追加設定が必要。
┗ 関数を「handler(){}」にする。
┗ 「deep: true」を設定する。
watchプロパティの使い方(ネストしてない変数の監視)
watch:{ プロパティ名: function(){ 処理 } }実例
(子)TextField.vue<template> <div> <v-col cols="12" sm="3"> <v-text-field v-model="text" /> </v-col> </div> </template> <script> export default { data(){ return{ //①監視する変数を定義 text: "初期値" }, watch:{ //②変数に対してwatchプロパティを設定 //textの値が変わったら以下処理を実行。 text: function (a){ this.$emit('childChange', a) } } } </script>関数内の
this.$emit('childChange', a)
┗ 関数内でプロパティを自分の呼び出す場合はthisが必要。
┗ $emitは子から親にデータを渡す
┗ $emit('親に渡すメソッド名', 引数)
┗ 引数は任意
┗ 引数には監視している変数が入る
▼引数のメリット
引数を使わずにデータを指定して渡す方法もあるが、引数を使った記述の方がより簡易的。引数ありwatch:{ text: function (a){ this.$emit('childChange', a) } }引数なし(データを直接指定)watch:{ text: function (){ this.$emit('childChange', this.text) } }(親)parent.vue<template> <v-app> <TextField <!--子からイベントを受け取り、親のイベントに引き継ぐ--> @childChange="parentChange" /> <p> ・v-modelのデータ: {{text}} </p> </v-app> </template> <script> import TextField from "./TextField" export default { name: "Parent", components:{ TextField }, data(){ return{ text:'初期値' } }, methods:{ //親のイベントを実行 parentChange(text){ this.text = text console.log("text-filedが変更されました") } } } </script>これで、text-fieldへの入力値がリアルタイムで反映される。
ネストした変数データの監視
変数がネストしている場合、以下2つの設定が必要。
(1) functionをhandlerに変更
(2) deep: true を設定※watchプロパティの中が入れ子になるため、追加で{}が必要。
watch:{ プロパティ名:{ handler(){ 処理 }, deep: true } }
実例
(子)TextField.vue<template> <div> <v-col cols="12" sm="3"> <v-text-field v-model="text.sub" /> </v-col> </div> </template> <script> export default { data(){ return{ //①監視する変数を定義(入れ子) text: { first: "初期値", sub: "subテキスト"} } }, watch:{ //②変数に対してwatchプロパティを設定 text:{ handler(a){ this.$emit('childChange', a) }, deep: true } } } </script>parent.vue<template> <v-app> <TextField <!--子からイベントを受け取り、親のイベントに引き継ぐ--> @childChange="parentChange" /> <p> ・v-modelのデータ: {{text}} </p> </v-app> </template> <script> import TextField from "./TextField" export default { name: "Parent", components:{ TextField }, data(){ return{ text:'初期値' } }, methods:{ parentChange(text){ //受け取った入れ子の変数のsubを使う this.text = text.sub console.log("text-filedが変更されました") } } } </script>
▼初期状態
▼文字入力後
文字を入力する毎に、親で設定したメソッドが実行されている。
変更があったタイミングでwatchが作動するため、初期値は反映されていない。
immediate: true
を追加で記述することで、初期値から反映することができる。
immediate: true
(子)TextField.vuewatch:{ text:{ //初期値からデータ連動 immediate: true, handler(a){ this.$emit('childChange', a) }, deep: true } } }
- 投稿日:2020-10-27T11:23:45+09:00
Vue入力値変更と変化の検出 & 子から親にデータを渡す方法(v-model、@change)
Vue入力値変更と変化の検出 & 子から親にデータを渡す方法(v-model、v-on:change)
自分のテンプレート内での変化を検出するパターンと、子テンプレートでの変化を検出し親に情報を渡すパターンの2つを考えてみる。
自分のテンプレート内での変化を検出
手順
①タグにv-modelを設定する (v-model="変数") ↓ ②dataオプションで変数を定義する ↓ ③タグに@changeイベントを設定する(@change="イベント名") ↓ ④methodsオプションで処理を定義する①と②、③と④がそれぞれ対となる。
※注意点
・v-modelで指定した変数の値と画面入力が連動する
・「@」は「v-on:」の省略形
@change
=v-on:change
vue<template> <div> <v-col cols="12" sm="3"> <v-text-field <!--①v-modelを設定--> v-model="text" <!--③changeイベントを設定--> @change="applyChanges" /> </v-col> <p> <!--v-modelのデータ連動確認用--> ・v-modelのデータ: {{text}} </p> </div> </template> <script> export default { data(){ return{ //②v-model用の変数を定義 text: "初期値" } }, methods:{ //④changeイベント用のメソッドを定義 applyChanges(){ console.log("text-filedが変更されました") } } } </script>
実行結果の確認▼変更前の状態。
▼変更後の状態
v-modelでデータが連動。changeイベントが発火している。
子から親へのデータ受け渡し
大まかな流れは、子テンプレでデータ発信を準備し、親側で受け取る用意をする。
子:①タグにv-modelを設定 (v-model="変数") ↓ 子:②dataオプションで変数を定義 ↓ 子:③タグに@changeイベントを設定(@change="イベント名") ↓ 子:④methodsオプションで発信処理を定義 (★親に引き渡すイベントと変数) ↓ 親:⑤受け取ったイベント名を属性で定義し親のイベントに引き継ぐ (@受けとたイベント名 = "親のイベント名") ↓ 親:⑥methodsオプションで処理を定義 (引数で子の変数を受け取る) ↓ 親:⑦受け取ったデータをいれる変数を定義(dataオプション) ↓ 親:⑧受け取ったデータを画面上に表示※注意点
・子のmethods内で、プロパティを呼び出す場合は「this」をつける。
┗ this.$emit('受け渡すメソッド名', 変数)
┗ this.プロパティ名
実例
(子)TextField.vue<template> <div> <v-col cols="12" sm="3"> <!--①v-modelを設定--> v-model="text" <!--③changeイベントを設定--> @change="applyChanges" /> </v-col> </div> </template> <script> export default { data(){ return{ //②v-model用の変数を定義 text: "初期値" } }, methods:{ //④methodsオプションで発信処理を定義 applyChanges(){ //データを変数で渡すため変数を定義 const text = this.text //第1引数でイベント名、第2引数で変数を渡す this.$emit('childChange', text) } } } </script>(親)parent.vue<template> <v-app> <TextField <!--⑤受け取ったイベント名を属性で定義し親のイベントに引き継ぐ--> @childChange="parentChange" /> <p> <!--⑧受け取ったデータを画面上に表示--> ・v-modelのデータ: {{text}} </p> </v-app> </template> <script> import TextField from "./TextField" export default { name: "Parent", components:{ TextField }, data(){ return{ //⑦受け取ったデータをいれる変数を定義 //子の初期値と合わせるため同じ値をいれる text:'初期値' } }, methods:{ //⑥methodsオプションで処理を定義 parentChange(text){ this.text = text console.log("text-filedが変更されました") } } } </script>
実施結果
▼画面入力後
v-modelで変更した値が適用されている。
親側で定義したメソッドも発火している。ただし、上記いずれの例も、changeイベントでmethodを発火させるため、変更を確定(Enter)しないと反映されない。
入力内容を即時反映したい場合はwatchプロパティを使う。
- 投稿日:2020-10-27T07:05:44+09:00
絵文字APIの使い方メモ
絵文字のAPIの使い方
絵文字用のAPIの使い方がよく分からなかったので、検索してみたら、下記の記事が存在。
http://rdrgn.hatenablog.com/entry/2017/01/20/000000まずは、emojione.min.jsとemojione.min.cssを読み込む必要がある。
実際に使ってみた
実際に動かした結果は下記の通り。emojione.min.jsはimgタグを返している。
https://qiita.com/heihei15408697/items/e830cae0a967346f6650返却されたimgタグをうまく出力したかったけど、そこの扱いがうまくいかず、imgタグの文字列をそのまま出力して終わっている。
出力する方法をどこかで調べて、ちゃんと出るようにしたい。
- 投稿日:2020-10-27T07:02:54+09:00
フロントエンド(TypeScript, Three.js)のみで動く、Molecular Visualizerの開発(その2)
概要
詳しくはその1を参照ください
現状
(前回との)更新内容
画面構成を部分的にVue.js化
headerにCSSでhover時の色の変化を加えた。
他細かな修正とまだ記事にしてない機能の追加分子構造データクラスとパーサー
現状対象としている構造データは、原子が3D空間上に点在していて、任意の原子間に結合が存在している。
つまりデータ構造としては、原子のリストと、結合のリストと、それのコンテナである。
これを愚直に抽象化したインターフェースを次のように設計した。AtomにはelementType以外にFFtypeなどが必要ではないかという話は、いったんおきましょう。Atom インターフェース(./src/systems/system.ts)
enum ElemType{ ANY,H,He,Li,Be,B,C,N,O,F,Ne, } export type Position = Array<number>; export type Name = string; export interface IAtom{ position: Position; name: Name; element: ElemType; index: number; }Bond インターフェース(./src/systems/system.ts)
export type BOND_TYPE= number; export interface IBond<AtomType extends IAtom = IAtom>{ atoma: AtomType; atomb: AtomType; vector: number[]; distance: number; position: Position; }System インターフェース(./src/systems/system.ts)
export interface ISystem<AtomType extends IAtom,BondType extends IBond<AtomType>>{ /**getterでの実装を禁ズ */ systemName: string; natoms: number; nbond: number; getNames(): string[]; getPositions(): Position[]; getElements(): ElemType[]; getAtom(index:number): AtomType; atomIndexOf(name:string): number; getAtoms():Generator<AtomType,string,unknown>; getBond():Generator<BondType,void,unknown>; }実現クラスは設計では一つしか作らない予定であるため、インターフェースクラスを用意する意味はほとんどないが、ひとまず経由することとした。
このSystemクラスは、描画するクラスに引き渡され描画のためのオブジェクトの作成や、IAtom.nameを使ってクリックイベントの対象物の識別に使われる(予定)。
- 投稿日:2020-10-27T00:10:08+09:00
vue3を導入しようとして挫折した話
はじめに
実PRJでvue3を導入しようとしたけど、PRJ途中で断念した話をまとめます。
巷ではCompositionAPIやら複数の双方向データバインディングやら話題ありますが、その辺りには到達することなく断念することになりました。とはいえ少し触ったので、「お、これいけてるやん!」というものも紹介しつつ、なぜ断念したかを綴ります。vue 3
「Vue 3」は、JavaScriptフレームワークVue.jsの次期バージョンで、2016年にリリースされた「Vue 2」以来のメジャーバージョンアップ。vue 2から記載方法が変わった点もあるが、割と互換性もある(気がした)
便利だと思った機能
v-on:click
をcomponentに指定しても動くようにvue 2までだと、componentに対して
v-on:click
を指定しても動作しません動かない<template> <div> <childComponent v-on:click="test" /> </div> </template> <script> import childComponent from "@/components/chiledComponent"; export default { components: { childComponent, }, methods: { test(): { alert("test") } } } </script>なので、下記のように記載する必要がありました。
これは動く<template> <div> <childComponent v-on:click.native="test" /> </div> </template> <script> import childComponent from "@/components/chiledComponent"; export default { components: { childComponent, }, methods: { test(): { alert("test") } } } </script>これがvue 3だと
vue3だと動く<template> <div> <childComponent v-on:click="test" /> </div> </template> <script> import childComponent from "@/components/chiledComponent"; export default { components: { childComponent, }, methods: { test(): { alert("test") } } } </script>のように記述することができます。
なのでタグが純粋なhtmlタグなのかcomponentタグなのか気にせず開発を進めることができます。build時の環境変数指定が楽に
開発時はdev環境、ユーザーテスト環境、本番環境などの環境ごとにAPIの向き先など環境変数を変更したいニーズがあると思います。vue 2では環境変数がデフォルトではconfigディレクトリ以下にそれぞれ
- dev.env.js
- prod.env.js
- test.env.js
が用意されていますが、もっと環境を分けたい場合はwebpackコンフィグファイルあたりをいじる必要がありました。
Nuxt.jsではcrossenvやdotenvをnpm i
してnuxt.config.jsファイルをいじるようなことをやってたかと思います。
vue 3ではその辺りをかなり良しなにやってくれたのが感動しました。
下記手順で環境ごとのbuildを簡単に作成できます。1. プロジェクト直下に.env.環境変数の命名で環境変数ファイルを作成
Project├─.env.production ├─.env.uat └─.env.dev2. 「.env.環境変数」のファイルにVUE_APP_XXXの命名規則で変数を定義する
変数のプレフィックスに
VUE_APP_
を記載するのを忘れないようにしましょう。env.devVUE_APP_REST_URL=https://testapi/v1/ VUE_APP_S3BACKET_URL=http://bucket.s3-ap-northeast-1.amazonaws.com3. package.jsonの
scripts
に下記を追記--mode 以降を.env.環境変数の環境変数と対応させる
package.json{ "scripts": { "build:dev": "vue-cli-service serve --mode dev", "build:uat": " vue-cli-service serve --mode uat", "buld:production": "vue-cli-service serve --mode producion" } }.vue内では
process.env.VUE_APP_REST_URL
のように記載すれば環境変数を参照できるオブジェクトや配列のバインディング改善
vue 2までの場合、配列要素の更新やオブジェクトのプロパティ追加削除を画面に反映させるには
Vue.set
やVue.delete
で間接的に操作する必要があったが、vue 3ではそれが不要となった。vue3/* これで更新できる */ this.array[0] = this.inputValueなんでvue3を諦めたの?
vue3のメジャーアップデートの恩恵にはあまり与っていないにせよ、上記だけでもなかなかメリットがあるように見られたが、下記のような流れで断念。
デザイナ「そういえばこのデザインbootstrap前提で作ってるよ」
そうなんです、2020年9月時点ではbootstrap-vueがvue3に対応していなかったのです。
でも、ここまでであれば開発チームはまだ心は折れません。所詮デザインなんてCSSで頑張ればbootstrapなんていらないよ。デザイナ「ダイアログはこのライブラリのイメージで作ったから、ライブラリ入れたら一発だよ」
bootstrapの流れからお察しの通り、ダイアログのライブラリも残念ながらまだvue3には対応していませんでした。
ここで開発メンバーも自前でダイアログのデザイン実装は厳しいと根を上げ、PRJ途中でNuxtに移行(笑)したのでした。
じゃあどうするのが正解だったの?
ここはぶっちゃけあんまり解はないのかなと思います。プロジェクトの納期を優先するのであれば、実績やリファレンスが豊富な安定版を使うべきだし、エンジニアとして最新を追い求めたいのであれば多少困難でも最新版を積極導入すればいいと思います。技術力がとても高ければ最新入れても大抵のことは独力解決できるだろうし。
今回に関してはPRJの早い段階で見切りを付けられたので進捗にもさほど影響しなかったし、少しだけだけど最新バージョンをいろいろ調べつつ悩みつつ触ることで多少なりとも知見が増えたので、結果的には良かったのでは、と個人的には思っています。
vue 3に世の中が追いついてきたらまたPRJ導入を検討したいなと思いました。