Vue.js 勉強メモ
■導入
headに
code:js
と書けば最新版が使える(本番環境ではバージョン表記を推奨)
■基本
code:js
<html lang="ja">
<head>
</head>
<body>
<div id="counter"> //#counterを設置
Counter: {{ counter }} //counterの内容を表示
</div>
</body>
<script>
const Counter = { //Counterというアプリを作成
data() { //初期データの設定
return {
counter: 0 //変数counterの内容を0に設定
}
},
mounted() { //更新(ライフサイクルフック)
setInterval(() => { //1秒ごとにカウンタ+1
this.counter++
}, 1000)
}
}
Vue.createApp(Counter).mount('#counter') //<-Counterというアプリを#counterにアタッチ
</script>
</html>
■ディレクティブとは
◆基本形
<ディレクティブ>:<文字列1>="<文字列2>"
ディレクティブは Vue によって提供された特別な属性であることを示すためにv-接頭辞がついています。
■タグの要素(attribute)にバインド(v-bind)
v-bind:<要素名>="<変数名>"
or
:<要素名>="<変数名>"
タグの要素の内容が常に変数の内容を反映するように「バインド」する。ただしこの方法は一方向なので、値を反映したINPUTタグ内の内容をユーザーが変更しても変数の内容には反映されない。
例
code:js
<div id="bind-attribute">
<span v-bind:title="message"> //title(カーソルを重ねたときにポップアップする文字列)の値を変数で設定
なんとかかんとか
</span>
</div>
<script>
...
const AttributeBinding = {
data() {
return {
message: 'このページを表示した時刻は' + new Date().toLocaleString()
}
}
}
...
■イベントハンドラ(v-on)
v-on:<イベント名>="<メソッド名>"
or
@<イベント名>="<メソッド名>"
このタグ上で<イベント>が発生したとき<メソッド>が実行されます。
◆例
code:js
<button @click="greet">Greet</button> <-- methods内のgreet()が呼ばれる
...
methods: {
greet(event) {
alert(event.target.tagName) <- event.targetで発火したオブジェクトを取得できる
}
}
<button @click="say('hi')">Say hi</button> <-- 引数指定も可能
<button @click="one(), two()">One,Two</button> <-- 複数のメソッドを順に呼ぶ
<button @click="counter += 1">Add 1</button> <-- 文を直接書いてもOK
$event変数でイベント情報そのものを渡せます
code:js
<input type="text" @click="warn($event)" @keydown="warn($event)" />
...
methods: {
warn(event) {
console.log(event) <--クリックすればPointerEvent,キー入力すればkeyboardEvent
}
}
◆例)
code:js
<div id="event-handling">
<p>{{ message }}</p>
<button v-on:click="reverseMessage">Reverse Message</button>
</div>
...
const EventHandling = {
data() { //初期値の宣言
return {
message: 'Hello Vue.js!'
}
},
methods: { //メソッドの宣言
reverseMessage() { //文字列が逆順になるメソッド
this.message = this.message
.split('')
.reverse()
.join('')
}
}
}
Vue.createApp(EventHandling).mount('#event-handling')
■双方向にバインド
<input v-model="<変数名>">
valueの値が<変数>にリアルタイムに反映されます
※INPUT type="text"ならvalue。BUTTONなら?CHECKBOXなら?TEXTAREAなら?
◆例)
code:js
<div id="two-way-binding">
<p>{{ message }}</p>
<input v-model="message" /> //<-INPUTに入力した内容がmessageとして上の<p>内のと共有化される
</div>
...
const TwoWayBinding = {
data() {
return {
message: 'Hello Vue!'
}
}
}
Vue.createApp(TwoWayBinding).mount('#two-way-binding')
■条件分岐
v-if:<変数名>
>> <変数>がtrueのときタグは描画されます。falseならされません。
◆例)------------------------------------------------------------
<span v-if="seen">Now you see me</span>
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■ループ
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
v-for="<要素> in <配列>"
>> 配列内の要素の数だけそれぞれ描画する
◆例)------------------------------------------------------------
<div id="list-rendering">
<ol>
<li v-for="todo in todos"> <-- todos配列の中をひとつずつ取り出しtodoとして<li>を描画
{{ todo.text } <--取り出した各todoオブジェクトのtext要素を参照している
</li>
</ol>
</div>
..................................................................
const ListRendering = {
data() {
return {
todos: [ <-- 3つのオブジェクト配列になっており、それぞれがtext要素を持っている
{ text: 'Learn JavaScript' },
{ text: 'Learn Vue' },
{ text: 'Build something awesome' }
]
}
}
}
Vue.createApp(ListRendering).mount('#list-rendering')
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■コンポーネント
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆書き方その1----------------------------------------------------
<div id="<アプリid>">
<<コンポーネントタグ名> v-bind:<要素名>="<オブジェクト>" />
</div>
..................................................................
const app = {
data() {
return {
// 初期値の定義
<オブジェクト>: <値>
}
},
components:{
"<コンポーネントタグ名>": {
template: "<テンプレート文字列>" //テンプレート内で要素の値を参照できる
}
}
}
Vue.createApp(app).mount("<アプリid>")
-------------------------------------------------------------------
◆書き方その2----------------------------------------------------
(同上)
..................................................................
const appobj = {
data() {
return {
// 初期値の定義
<オブジェクト>: <値>
}
}
}
const <コンポーネントオブジェクト名> = {
template: "<テンプレート文字列>" //テンプレート内で要素の値を参照できる
}
const app = Vue.createApp(appobj)
app.component("<コンポーネントタグ名>", <コンポーネントオブジェクト名>)
app.mount('<アプリid>')
-------------------------------------------------------------------
<コンポーネント名>タグを記述するたびに
<テンプレート文字列>が代わりに出力される
テンプレート内で要素を参照できる
data()の中でオブジェクトの初期値が与えられる
↓
コンポーネントタグ内でコンポーネントに要素=オブジェクトとして渡される
↓
コンポーネント側で要素名でオブジェクトを受け取り、処理する
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■コンポーネント…スロット
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
HTML
<todo-button>
Add todo
</todo-button>
..................................................................
テンプレート
<button class="btn-primary">
<slot></slot>
</button>
..................................................................
結果
<button class="btn-primary">
Add todo
</button>
-------------------------------------------------------------------
※テンプレート記述時に<slot>はコンポーネントタグ内のinnerHTMLを指す
※テンプレートに<slot>を書かなければHTMLの内容は無視される
◆フォールバックコンテンツ(デフォルト値)
HTML
<submit-button></submit-button>
..................................................................
テンプレート
<button type="submit">
<slot>Submit</slot>
</button>
..................................................................
結果
<button type="submit">
Submit
</button>
-------------------------------------------------------------------
※テンプレートの<slot>内コンテンツはHTMLに何も書かなかった場合のデフォルト値になる
※HTMLになにかを書けばテンプレート<slot>の内容は無視される
HTML
<base-layout>
<template v-slot:header> <-- 名前を指定して各name要素を持った<slot>要素にアクセスします
<h1>ページタイトルとか</h1>
</template>
<template v-slot:default> <-- "default"でname要素のない<slot>にアクセスします
<p>メインコンテンツ</p>
</template>
<p>著作権情報とか</p>
</template>
</base-layout>
..................................................................
テンプレート
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
..................................................................
結果
<div class="container">
<header>
<h1>ページタイトルとか</h1>
</header>
<main>
<p>メインコンテンツ</p>
</main>
<footer>
<p>著作権情報とか</p>
</footer>
</div>
◆スコープ付きスロット
◆デフォルトスロットしかない場合の省略記法
◆スロットプロパティの分割代入
◆動的なスロット名
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■アプリケーションインスタンス
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
const app = Vue.createApp({
/* options */
})
-------------------------------------------------------------------
const app = Vue.createApp({})
app.component('SearchInput', SearchInputComponent)
app.directive('focus', FocusDirective)
app.use(LocalePlugin)
-------------------------------------------------------------------
Vue.createApp({})
.component('SearchInput', SearchInputComponent)
.directive('focus', FocusDirective)
.use(LocalePlugin)
-------------------------------------------------------------------
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■ルートコンポーネント
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
const RootComponent = {
/* オプション */
}
const app = Vue.createApp(RootComponent)
const vm = app.mount('#app')
慣習として、コンポーネントのインスタンスを参照するのに
vm (ViewModel の略) という変数を使うことがよくあります。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■コンポーネントインスタンスのプロパティ
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
const app = Vue.createApp({
data() {
return { count: 4 }
}
})
const vm = app.mount('#app')
console.log(vm.count) // => 4
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■ライフサイクルフック
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
例えば created フックは、インスタンスの作成後にコードを実行するために使用できます:
Vue.createApp({
data() {
return { count: 1 }
},
created() {
// this は vm インスタンスを指す
console.log('count is: ' + this.count) // => "count is: 1"
}
})
全てのライフサイクルフックは、呼び出し元である現在アクティブなインスタンスを指す
this コンテキストとともに呼ばれます。
※アロー関数「()=>」をオプションのプロパティやコールバックに使用しないでください。
アロー関数は this を持たないためエラーを起こします。
**********************************
■テンプレート構文
**********************************
{{ これ }} をMustacheと呼ぶ
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆テキスト展開
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
<span>Message: {{ msg }}</span>
※v-once ディレクティブを使用することで、一度だけ展開することができます。
<span v-once>This will never change: {{ msg }}</span>
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆生の HTML
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
...
rawHtml: '<span style="color: red">This should be red.</span>'
...
<p>{{ rawHtml }}</p> <-- エスケープされて表示される
<p v-html="rawHtml"></p> <--タグが反映される
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆属性
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
Mustache は、HTML 属性の内部で使用することはできません。
代わりに、v-bind ディレクティブを使用してください:
誤) <div id="{{ dynamicId }}"></div>
正) <div v-bind:id="dynamicId"></div>
バインドされた値が null や undefined の場合
その属性はなかったことにされます
(※属性に"null"や"undefined"が設定されるわけではない)
ただし空文字("")の場合は属性は存在します
<button v-bind:disabled="isButtonDisabled">Button</button>
この場合、isButtonDisabled==""でもdisabled属性は存在するのでbuttonは無効化されます
無効化を防ぐにはfalse,null等を入れる必要があります
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆JavaScript 式の使用
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
全てのデータバインディング内部でJavaScript式をサポートします
現在のアクティブなインスタンスのデータスコープ内で評価されます。
{{ number + 1 }}
{{ ok ? 'YES' : 'NO' }}
{{ message.split('').reverse().join('') }}
<div v-bind:id="'list-' + id"></div>
※それぞれのバインディングは、単一の式だけ含むことができます。
{{ var a = 1 }} <-- これは文であり、式ではありません
{{ if (ok) { return message } }} <-- フロー制御はいずれも動作しません。三項演算子を使用してください。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆動的引数
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
角括弧で囲むことで JavaScript 式をディレクティブの引数に使うこともできます:
{ attributeName : "href" }
↓同義
<a href="url"> ... </a>
{ eventName : "focus" }
↓同義
<a onfocus="doSomething"> ... </a>
※スペースや引用符のような一部の文字は、HTMLの属性名として不正な文字です。
※属性名はブラウザが強制的に小文字にするため、キー名を大文字にするのは避けるべきです:
<a v-bind:someAttr="value"> ... </a> <--"someattr" プロパティがない場合、このコードは動作しません。 ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆修飾子
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
<form v-on:submit.prevent="onSubmit">...</form>
↓
event.preventDefault()
?????????????
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■データプロパティ
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
Vue は新規コンポーネントのインスタンスを作成する際に、オプションのdata関数を呼び出します。
返されたオブジェクトは$data として格納します。
const app = Vue.createApp({
data() {
return { count: 4 }
}
})
const vm = app.mount('#app')
..................................................................
console.log(vm.$data.count) // => 4
console.log(vm.count) // => 4
// vm.count に値を代入すると、 $data.count も更新
vm.count = 5
console.log(vm.$data.count) // => 5
// ... 逆もまた同様
vm.$data.count = 6
console.log(vm.count) // => 6
コンポーネント内で使用されるプロパティはdata()で宣言する必要がある
後から追加されたプロパティは自動的に追跡されません
※Vueは$と_をプレフィックスとして予約しています。これらの文字からはじまる名前を使うことは避けるべきです。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■メソッド
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
コンポーネントのインスタンスにメソッドを追加するには、
methods オプションを使います。
これは必要なメソッドを含むオブジェクトでなければなりません:
const app = Vue.createApp({
data() {
return { count: 4 }
},
methods: {
increment() {
// this はコンポーネントインスタンスを参照
this.count++
}
}
})
const vm = app.mount('#app')
..................................................................
console.log(vm.count) // => 4
vm.increment()
console.log(vm.count) // => 5
..................................................................
<button @click="increment">Up vote</button> <--クリックされるとincrementメソッドが呼ばれます。
..................................................................
<span :title="toTitleDate(date)"> <--テンプレートから直接メソッドを呼び出すこともできます。
{{ formatDate(date) }}
</span>
※テンプレートから呼び出されたメソッドは、データの変更や非同期処理の発火などの副作用があってはなりません。
もしそのようなことをしたくなったら、代わりに ライフサイクルフック を使うべきです。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■Debounce (デバウンス) と Throttle (スロットル)
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
throttleとdebounceとは、簡単に言うと間引き処理の一種です。
throttleとは
連続して大量に繰り返される処理を一定感覚で間引くものです。
debounceとは
連続して大量に繰り返される処理が指定時間内に何度発生しても最後の1回だけ実行するものです。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■算出プロパティ/算出 Setter 関数
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
data() {
return {
firstName: "Shinji",
lastName: "Takeda"
}
},
computed: {
fullName: {
get() {
return this.firstName + ' ' + this.lastName
},
set(newValue) {
const names = newValue.split(' ')
}
}
}
この状態で vm.fullName = 'John Doe' を実行すると setter 関数が呼び出され、
その結果 vm.firstName と vm.lastName が更新されます。
※つまり単なる代入式で他の値に加工ができる
メソッドと比較して算出プロパティは計算結果がキャッシュされるという違いがあります。
※つまり上の例だと何度fullnameを参照しても計算量は増加しない
computed: {
now() {
return Date.now() <-- 実行時に計算され、以降何度now()を呼び出しても同じ値を戻す
}
}
methods: {
now() {
return Date.now() <-- 呼び出すたびに違う結果が戻る
}
}
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■ウォッチャ
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
<div id="watch-example">
<p>
Ask a yes/no question:
<input v-model="question" />
</p>
<p>{{ answer }}</p>
</div>
..................................................................
<!-- ajax ライブラリや汎用ユーティリティメソッドのコレクションなどの -->
<!-- 豊富なエコシステムがすでに存在するため、それらを再発明しないことで -->
<!-- Vue のコアは小規模なまま保たれています。これは、使い慣れたものを -->
<!-- 自由に使うことができる、ということでもあります。 -->
<script>
const watchExampleVM = Vue.createApp({
data() {
return {
question: '',
answer: 'Questions usually contain a question mark. ;-)'
}
},
watch: {
// question が変わるたびに、この関数が実行される
question(newQuestion, oldQuestion) {
if (newQuestion.indexOf('?') > -1) {
this.getAnswer()
}
}
},
methods: {
getAnswer() {
this.answer = 'Thinking...'
axios
.then(response => {
this.answer = response.data.answer
})
.catch(error => {
this.answer = 'Error! Could not reach the API. ' + error
})
}
}
}).mount('#watch-example')
</script>
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■コンポーネントTips
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
親から子に値を渡すにはタグ属性とpropsを使う
子から親に値を戻すには?
propsに親のメソッドを渡してしまえば良い
◆親
<child :parentFunction="recieveFunction" />
...............
methods: {
recieveFunction(value) {
console.log(value)
}
}
◆子
<button @click="returnValue">click</button>
...............
props: {
parentFunction: Function
},
method: {
returnValue(value) {
this.parentFunction(value)
}
}
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■CSS:クラスの適用
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
v-bind:classにオブジェクトを渡してCSSクラスを動的に切り替える
これは従来の使い方
<div v-bind:class="classobj"></div>
..................................................................
data: { return {
classobj: "foo-class"
}}
<div class="static" v-bind:class="classobj"></div>
のように通常のclass記述と同居もできます
クラス名を列挙した配列も渡せます
<div v-bind:class="classLIst"></div>
..................................................................
data: { return {
}}
<div v-bind:class="{ foo-class: isActive }"></div>
foo-classクラスの有無がisActiveの真偽によって決まる
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■CSS:スタイルの適用
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
..................................................................
data: { return {
activeColor: 'red',
fontSize: 30
}}
※fontSize = xxx. のように直接操作できます
or
<div v-bind:style="styleObject"></div>
..................................................................
data: { return {
styleObject: {
color: 'red',
fontSize: '13px'
}
}}
※styleObject.fontSize = xxx +"px" のように直接操作できます
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■トランジション
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■API
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆グローバルAPI
- createApp( <オプションオブジェクト> {, <ルートプロパティ>} )
オプションに基づいてアプリケーションappを返します
2番めの引数でオプションで定義したプロパティをその場で定義できます
const app = createApp(
{ username: 'Evan' }
)
◆オプション
- data : function
アプリケーションで監視するデータオブジェクトを返す関数です
この関数内で何らかの作業をするのはおすすめしません。
ただオブジェクトを返すのみが望ましいです
const app = createApp({ data() { return data } }).mount('#app')
- props : Array<string>|function
親コンポーネントからデータを受け取るために公開されている属性のリストかハッシュです。
props: {
size, // <-- 宣言のみ
height: Number, // <-- 型チェック
age: { // <-- さらに詳細
type: Number, // <-- 型。String、Number、Boolean、Array、
// Object、Date、Function、Symbolのどれか。
// 違っていると警告が出る
default: 0, // <-- プロパティのデフォルト値を指定します。
required: true, // <-- プロパティが必須かどうかを定義します。
validator: value => { // <-- 代入された値をチェックするためのカスタム関数。
return value >= 0 // この関数が false を返す場合コンソールに警告を出します。
} //
}
}
- computed :
算出プロパティ。
component
config
directive
mount
provide
unmount
use
version
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
■小ネタ
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
◆importが動かない
外部のスクリプトをブラウザから呼び出したい時。
importはmoduleタイプのスクリプトではないと動かない。普通にブラウザで呼び出すと
Uncaught SyntaxError: Cannot use import statement outside a module
と出る。
Node.jsを使っているときは問題にならない。
また似たようなコマンドの require はNode.js専用のコマンド。ブラウザでは動かない
→対策
<script type="module">
と書く。この時、自動的にStrictモードになるので変数の宣言忘れに注意。その後
import ???? from "./****.js"
とする。.jsはファイル名を直接指定する。
uncaught SyntaxError: The requested module './****.js' does not provide an export named 'default'
と出るとき(default exportがされてない時)は
import {????} from "./****.js"
も試す。
MIDIParser
ファイルを開く前にパーサーを設定しておくのね
つまりmounted内でMIDIparser.parse(<"File"Node>, callback )という感じで仕込んでおく