JavaScriptの概念(念頭におくと理解しやすい)
- オブジェクトは、プロパティ(状態・属性・データ)+メソッド(振る舞い)で成り立っている。
勿論、プロパティの無いもの、メソッドがないものを作ることも可能 - オブジェクトは、プロパティにメソッドを付けたもの
この概念がわかっていれば、理解しやすいと思う。
例えば、関数とメソッドの違いも、このことが理解できていればわかるはず。
完成イメージ
ソースコード
ドットインストールを参考にしてます。
https://dotinstall.com/lessons/basic_vuejs_v2
※プレミアム会員のみ閲覧可能
↓細かい説明はコメントアウトでまとめてます↓
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>My Sample Vue App</title>
<link rel="stylesheet" href="css/styles.css">
</head>
<body>
<div id="app" class="container">
<h1>
<!-- @clickはv-on:clickと同じ。イベントに紐付ける -->
<button @click="purge">完了したTODOを削除</button>
My TODO<span class="info">({{remaining.length}}/{{todos.length}})</span>
</h1>
<ul>
<li v-for="(todo, index) in todos">
<!-- v-modelでidDoneの値を引っ張ってきており、
値がある=trueであれば、checkboxがチェックされている状態になる -->
<!-- 逆にチェックをview側でしたら、todo.isDoneがtrueになる -->
<input type="checkbox" v-model="todo.isDone">
<!-- :classはv-bind:classと同じ。プロパティに応じてクラスを変更させる -->
<span :class="{done: todo.isDone}">{{todo.title}}</span>
<span @click = "deleteItem(index)" class="command">[x]</span>
</li>
<li v-show="!todos.length">Nothing to do</li>
</ul>
<!-- preventメソッドで、post通信しないようにする -->
<form @submit.prevent="addItem">
<input type="text" v-model="newItem">
<input type="submit" value="送信">
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="js/main.js"></script>
</body>
</html>
main.js
// 即時関数で囲いつつ、 user strictで厳密なエラーチェックをする
// vue.jsの特徴は双方向データバインディングである
(function() {
'use strict';
const vm = new Vue ({
//対象範囲
el: '#app',
// Vue インスタンスに組み込まれるdataオブジェクト
//dataキーの生成と宣言。
data: {
//newItemプロパティの生成
newItem: "",
// todosをキーとするオブジェクトを生成
todos: [{
title: "task1",
isDone: false
},{
title: "task2",
isDone: false
},{
title: "task3",
isDone: true
}]
},
// データが変わるのに応じて非同期やコストの高い処理を実行したいときに使用するプロパティ。watchを宣言。
watch: {
// 下の処理だと、配列に変更があった時のみ反応し、中身の変更までは見てくれない。
// todos: function(){
// localStorage.setItem('todos', JSON.stringify(this.todos));
// }
// todosに変更があった場合に呼ばれる関数を定義
// todosをキーとして(値は何でも良い)、todosの値をJSON形式で保存する
todos: {
handler: function(){
localStorage.setItem('todos', JSON.stringify(this.todos));
},
//;をつけるとエラーになる
deep: true
}
},
//アプリがページにマウントされるタイミングでページに読み込む
mounted: function(){
this.todos = JSON.parse(localStorage.getItem("todos"));
},
// Vue インスタンスに組み込まれるメソッドの集まり。methodsの宣言。
methods: {
addItem: function(){
let item = {
title: this.newItem,
isDone: false
};
// キーに当たるdata内のプロパティにthisでアクセスできる
// htmlのview側でpreventメソッドを使わない場合は、functionの引数にeを代入した上で、
// e.preventDefault();としてあげる。
if(this.newItem.length === 0){
return
}
this.todos.push(item);
this.newItem = "";
},
deleteItem: function(index){
if (confirm("are you sure?")){
this.todos.splice(index, 1)
}
},
purge: function(){
if (!confirm('delete finidhed?')){
return;
}
// this.todos = this.todos.filter(function(d){
// return !d.isDone;
// });
this.todos = this.remaining
}
},
// Vue インスタンスに組み込まれるcomputedプロパティ。computedの宣言。
computed: {
//remainigというプロパティで関数を作る
remaining: function(){
// filterメソッド→配列をレシーバーにとり
// 条件に一致したものだけ抽出して新しい配列に格納する
//引数のdは、this.todos配列の各valueを抽出している。
// let items = this.todos.filter(function(d){
// //isDoneがfalseの要素だけをreturnしている
// return !d.isDone
// });
// return items.length;
return this.todos.filter(function(d){
return !d.isDone;
})
}
}
})
})()
その他ポイント
- computedの結果はメモ化される(値が変わるまで再計算されない)。computedは表示の高速化に有用。
- {{}}の中で、プロパティのキーを指定できる。
<input type='text' v-model='a'>
<label><input type='radio' v-model='a' value='1'>壱</label>
<label><input type='radio' v-model='a' value='2'>弐</label>
<label><input type='radio' v-model='a' value='3'>参</label>
<input type='range' min='1' max='3' v-model='a'>
<input type='checkbox' false-value='1' true-value='2' v-model='a'>
<textarea v-model='a'></textarea>
{{a}}
const vm = new Vue({el: "#app", data: {a: null}})
vm.a = 1
これで、データが連動する。※別にnullでなく、インスタンス生成時に値をセットしても良い。
const vm = new Vue({
el: "#app",
data: {
a: 43300,
b: 1,
c: 5566,
d: 1
},
watch: {
b: function(v) { if (v < 0) { this.b = 0 } },
d: function(v) { if (v < 0) { this.d = 0 } },
},
computed: {
sum: function(){
return this.a * this.b + this.c * this.d
},
tax: function(){
return Math.ceil(this.sum * 0.08)
},
}
})
詳細は省くが、ここでポイントとなるのが、watchの使い方。watchの乱用はよくないが、
computedのプロパティに対し、ifで入力制限をかけたい時などに、上記のように記述して
条件分岐させる。
# trimメソッドでユーザの入力から空白を自動的に取り除くことができる
<input v-model.trim="msg">
{{ var a = 1 }} <!-- 不可: 文は書けない -->
{{ if (ok) { return message } }} <!-- 不可: ifなどの制御フローも書けない -->
{{ ok ? 'YES' : 'NO' }} <!-- OK: 三項演算子は書ける -->
{{140 - b.length}} <!-- OK: 表示に使う式は評価してくれる -->
<div class="help-block" :style="{color: a}">
<!-- :class,:styleを動的に変更できる -->
<!-- 属性を指定 {}で囲む必要がない。-->
<img :src="imageSrc">
input type="color"
<!-- 直感的に色指定が可能 -->
var vm = new Vue({
el: "#app",
data: {
h: 163,
s: 84,
l: 77,
},
computed: {
c: function() {
return `hsl(${this.h}, ${this.s}%, ${this.l}%)`
},
},
});
<div id="app">
<div class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label">色</label>
<div class="col-md-10">
<input class="form-control" v-model="c" :style="{backgroundColor: c}" />
<div class="help-block">
<input type="range" min="0" max="360" v-model.number="h" />
<input type="range" min="0" max="100" v-model.number="s" />
<input type="range" min="0" max="100" v-model.number="l" />
</div>
</div>
</div>
</div>
</div>
- computedのプロパティにエラーが出ないように、v-model.numberのnumberでユーザの入力を数値として自動的に型変換する。
[...'𠮷'].length // => 1
<!-- jsでたまに発生するlengthが期待通り動作しない時の回避法 -->
<div id="app">
<div class="btn btn-link" @click="a = !a">
{{label}}
</div>
<p v-show="a">
<img class="img-thumbnail" src="https://via.placeholder.com/256?text=CONTENTS" />
</p>
</div>
<!-- @clickに反応して、aのbooleanが反転する -->
<!-- aがtrueの時に、v-showが反応する -->
<!-- @clickがcomputedプロパティに紐づく例 -->
const vm = new Vue({
el: "#app",
data: {a: false},
computed: {
label: function() { return this.a ? "閉じる" : "もっと見る" },
},
});
<div id="app">
<div class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label">タイトル</label>
<div class="col-md-10">
<input class="form-control" v-model="a" />
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="btn btn-primary" :disabled="b">
Merge
</div>
</div>
</div>
</div>
</div>
const vm = new Vue({
el: "#app",
data: {a: "WIP: 親譲りの無鉄砲で小供の時から損ばかりしている"},
computed: {
b: function() {
return /\bWIP\b/i.test(this.a)
},
},
})
<!-- 正規表現で「wip」の単語があれば、trueになる。 -->
- testメソッドで文字列を正規表現でチェックし、booleanを返す。それを受け、
:disableが反応する。 - 正規表現でiを指定しているので大文字小文字は区別しない。
チェックを入れないと送信出来ない制限をかける
<div id="app">
<div class="form-horizontal">
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<label class="checkbox-inline">
<input type="checkbox" v-model="a">承認</input></label>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<div class="btn btn-primary" :disabled="!a">
送信
</div>
</div>
</div>
</div>
</div>
<input type="checkbox" v-model="a" @change="a = true">
承認
</input>
const vm = new Vue({
el: "#app",
data: {a: false},
})
-
disabled="disabled"にして、送信出来ないようになっている。
checkboxにチェックするかどうかでbooleanをv-modelで反応させている。 -
@change="a = true"を指定することで、常にtrueにしているため、このチェックボックスをチェックすると、解除出来ない。changeイベントを発火させると、ずっとtrueの仕様にしている。
パスワードのマスクを解除
<div id="app">
<div class="form-horizontal">
<div class="form-group">
<label class="col-md-2 control-label">パスワード</label>
<div class="col-md-10">
<input class="form-control" type="password" v-model="p" v-if="t == 'password'" />
<input class="form-control" type="text" v-model="p" v-if="t == 'text'" />
<div class="help-block">
<label class="checkbox-inline" >
<input type="checkbox" v-model="t" true-value="text" false-value="password"/>マスク解除</label>
</div>
</div>
</div>
</div>
</div>
const vm = new Vue({
el: "#app",
data: {p: "secret-password", t: "password"},
})
- checkboxでboolean評価し、その評価に合わせてv-ifで条件分岐し、
表示を出し分けしている。