vue.js CLIインストール
vue.jsの基本型
const app = Vue.createApp({
data: () => ({
key: '',
}),
})
app.mount('#app')
<!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">
<title>Document</title>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="app">
<p>
{{key}}
</p>
</div>
<script src="https://unpkg.com/vue@3.1.5"></script>
<script src="js/main.js"></script>
</body>
</html>
データオプションにオブジェクト・配列を設定
const app = Vue.createApp({
data: () => ({
message: 'Hello Vue.js!',
count: 0,
user:{
lastName: 'Nakamura',
firstName: 'Yoko',
prefecture: 'Kyoto'
},
colors:['red', 'blue', 'yellow']
})
}).mount('#app')
<div id="app">
<p>
{{message}} # Hello Vue.js!
{{count}} # 0
{{user.lastName}}{{user.firstName}} # NakamuraYoko
{{colors[0]}} # red
</p>
</div>
ディレクティブ(指令)
v-bind
<div id="app">
<input type="text" v-bind:value="message">
</div>
const app = Vue.createApp({
data: () => ({
message: 'Hello Vue.js!'
})
}).mount('#app')
v-bindにはいくつもオプションがあるようです。
v-bindのオプション(一部)
value: プロパティや変数の値を要素の属性にバインドします。
class: 要素に動的なクラスを追加または削除します。
style: 要素のスタイルを動的に設定します。
src: イメージのソースを動的に設定します。
href: リンクのURLを動的に設定します。
disabled: 要素の無効状態を動的に切り替えます。
key: 要素の一意の識別子を設定します。リスト内の要素の再レンダリングを制御するために使用されます。
また、v-bind:href="hoge"
を:ref="hoge"
のように省略して書くことができる。
v-bind:key(追記)
v-bind:keyは結構使いそうなので追記。
それぞれの要素にidを付与しておく必要がある場合、例えば、メモ機能を追加したり削除したりするときにこのidが付与されていないと特定できないので、使用する。
以下のように使用する。このように書くことで、それぞれの要素にidが付与されることとなる。
<ul>
<li v-for="item in items" v-bind:key="item.id">
{{ item.name }}
</li>
</ul>
data() {
return {
items: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' }
]
}
}
v-if(v-else/v-else-if)
<div id="app">
<p v-if="toggle">hello</p>
</div>
const app = Vue.createApp({
data: () => ({
toggle: false
})
}).mount('#app')
v-for
<div id="app">
<ul>
<l1 v-for="user in users">{{user}}</l1>
</ul>
</div>
const app = Vue.createApp({
data: () => ({
users: ["Taro", "Natsuki", 'Yuji']
})
}).mount('#app')
const app = Vue.createApp({
data: () => ({
users: {
firstName: "Taro",
lastName: "Fujisawa",
age: 30
}
})
}).mount('#app')
<div id="app">
<ul>
<l1 v-for="(value, key) in users">
{{key}}:{{value}}
</l1>
</ul>
</div>
v-on
v-on:click=>@clickと短縮形で書ける!!
<div id="app">
<button v-on:click="onClick">ボタン</button>
<p>{{now}}</p>
</div>
const app = Vue.createApp({
data: () => ({
now: '-'
}),
methods: {
onClick: function(){
this.now = new Date().toLocaleString()
}
}
}).mount('#app')
そのほか
- stop: クリックイベントの伝搬が止まります
<a v-on:click.stop="doThis"></a>
- submit: イベントによってページがリロードされません
<form v-on:submit.prevent="onSubmit"></form>
修飾子は繋げることができます
<a v-on:click.stop.prevent="doThat"></a>
値を指定せず、修飾子だけ利用することもできます
<form v-on:submit.prevent></form>
- capture: イベントリスナーを追加するときにキャプチャモードで使用。言い換えれば、内部要素を対象とするイベントは、その要素によって処理される前にここで処理されます。
<div v-on:click.capture="doThis">...</div>
- self: event.target が要素自身のときだけ、ハンドラが呼び出されます。言い換えると子要素のときは呼び出されません。
<div v-on:click.self="doThat">...</div>
また、v-onのオプションもたくさんあります。基本的にJSのイベントリスナーですね。わかりやすいです。
v-onのオプション
- click:
クリックイベントの処理をバインドします。 - input:
入力イベントの処理をバインドします。テキストボックスやテキストエリアの入力値の変更を検知する際に使用されます。 - submit:
フォームの送信イベントの処理をバインドします。 - keydown:
キーダウンイベントの処理をバインドします。キーボードのキーを押した瞬間に実行されます。 - keyup:
キーアップイベントの処理をバインドします。キーボードのキーを離した瞬間に実行されます。 - mouseover:
マウスオーバーイベントの処理をバインドします。要素にマウスが乗った瞬間に実行されます。 - mouseout:
マウスアウトイベントの処理をバインドします。要素からマウスが離れた瞬間に実行されます。
v-model
* データバインディングで、要素のvalue属性を更新する。
* イベントハンドリングで、受け取った値をデータに代入する。
- value, checked, selectedなどの属性は無視される。
- textareaなどでは、マスタッシュ構文が反映されないので、v-modelを使用する。
<div id="app">
<p>
<input type="text" v-model="message">
</p>
<p>
<input type="text" v-model="message">
</p>
</div>
const app = Vue.createApp({
data: () => ({
message: 'Hello Vue.js!'
})
}).mount('#app')
v-model 修飾子の種類
v-modelは修飾子により動作を変更できる。
- .lazy: バインドのタイミングを遅延させる
- .trim: 入力値から前後の空白を削除してからデータに代入
=>ユーザー名や商品情報など空白が入っているとよろしくない場合に使用するとよい。 - .number 入力値を数値型に変換してからデータに代入
コンポーネント
- 部品を作って、再利用できる。メンテナンスもやりやすくなる。
<div id="app">
<hello-component></hello-component>
</div>
const app = Vue.createApp({
data: () => ({
message: 'Hello Vue.js!'
}),
})
app.component('hello-component', {
template: '<p>Hello!</p>'
})
app.mount('#app')
コンポーネントのローカル登録
上の書き方はグローバルコンポーネントなのでスコープが広すぎる。したがって、ローカルコンポーネントにしてみる。
const helloComponent = {
template: '<p>Hello!</p>'
}
const app = Vue.createApp({
data: () => ({
message: 'Hello Vue.js!'
}),
components: {
'hello-component': helloComponent
}
})
app.mount('#app')
コンポーネントの命名
ハイフンを1つ以上含むケバブケースを利用しなければいけない。
○ hello-component
○ button-counter
✖️ hello
✖️ helloComponent
コンポーネントで動的な処理を行う
以下のようにローカルコンポーネントに関数を定義して、templateとして使用すれば、使いまわすことが可能になる。
const buttonCounter = {
template: '<div><span>count:</span><button @click="countUp()">{{count}}</button></div>',
data: ()=> ({
count:0
}),
methods: {
countUp: function(event){
this.count ++
}
}
}
const app = Vue.createApp({
data: () => ({
color: []
}),
components: {
'button-counter': buttonCounter
}
})
app.mount('#app')
<div id="app">
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
ToDoList
<!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">
<title>Document</title>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="app">
<h2>To Do List</h2>
<form v-on:submit.prevent>
<input type="text" v-model="newItem">
<button @click="addItem">追加</button>
</form>
<ul>
<li v-for="(todo, index) in todos">
<input type="checkbox" v-model="todo.isDone">
<!-- done:true/falseとすることで、trueだったらクラス名が付与されて、falseだと付与されない。 -->
<span v-bind:class="{done:todo.isDone}">{{todo.item}}</span>
<button @click="deleteItem(index)">削除</button>
</li>
</ul>
<pre>{{$data}}</pre>
</div>
<script src="https://unpkg.com/vue@3.1.5"></script>
<script src="js/main.js"></script>
</body>
</html>
const app = Vue.createApp({
data: () => ({
newItem: '',
todos:[],
}),
methods: {
addItem: function(e) {
if(this.newItem === "") return
let todo = {
item: this.newItem,
isDone: false,
}
this.todos.push(todo)
this.newItem = ""
},
deleteItem: function(index){
this.todos.splice(index, 1 )
}
}
})
app.mount('#app')
テンプレート構文
v-once
初回だけバインディングする
:API呼び出しなどを画面がロードされたときに行いたいが、それ以降の結果は変わらないので一回だけで良い
:初期値が変わらないということが分かっている場合、ページ更新のパフォーマンス向上
<div id="app">
<p v-once>{{message}}</p>
<button @click="reverseButton">reverse!</button>
</div>
const app = Vue.createApp({
data: () => ({
message: 'hello!world!',
}),
methods: {
reverseButton: function(e) {
this.message = this.message.split('').reverse().join('')
},
}
})
app.mount('#app')
v-pre
要素と全ての子要素のコンパイルをスキップする
・名前のマスタッシュタグを表示したい
・ディレクティブのない大量のノードをスキップし、コンパイルスピードを上げる
以下のように書くと"hello!wold!"ではなく、"{{hello]}"とマスタッシュがコンパイルされずに表示される。
<div id="app">
<p v-pre>{{message}}</p>
</div>
const app = Vue.createApp({
data: () => ({
message: 'hello!world!',
})
})
app.mount('#app')
v-html
- プレーンなHTMLを挿入する
- 指定した要素のinnerHTMLを更新できる
ただし、クロスサイトスクリプティング脆弱性を引き起こす可能性があるため、データは信頼できるデータのみ使用すること。例えば、不特定多数のユーザーが入力したコンテンツには絶対に使用しないこと。
<div id="app">
<p>{{message}}</p>
<p v-html="message"></p>
</div>
const app = Vue.createApp({
data: () => ({
message: 'hello!<span style="color:"red;">world!</span>',
})
})
app.mount('#app')
v-cloak
*cloak 覆い隠す
- インスタンスの準備が終わると取り除かれる
ページを表示開始してから、インスタンスの作成が終わるまでの間にマスタッシュタグなど、コンパイル前のテンプレートが表示されてしまうのを防ぐ。画面のちらつきをなくす!
[v-cloak]{
display: none;
}
<div id="app">
<p v-cloak>{{message}}</p>
</div>
const app = Vue.createApp({
data: () => ({
message: 'hello!world!',
})
})
app.mount('#app')
v-text
マスタッシュ構文の代わりに使用する
以下はどちらも同じ結果となる。
どちらでも良いが、マスタッシュの方が部分的に書き換えられるのでv-textよりも柔軟性がある。どちらにせよ統一した書き方をすること。
<div id="app">
<p>{{message}}</p>
<p v-text="message"></p>
</div>
JavaScript式
単一式のみ可能のため、let x = 1
やif式
などはシンタックスエラーになる。
<div id="app">
<p>{{message}}</p>
<p>{{number + 1}}</p>
<p>{{ok ? 'yes':'no'}}</p>
</div>
const app = Vue.createApp({
data: () => ({
message: 'hello!world!',
number : 100,
ok : true,
})
})
app.mount('#app')
算出プロパティ computed
関数によって算出したデータを返すことができるプロパティ
const app = Vue.createApp({
data: () => ({
message: 'hello!world!',
}),
computed: {
reversedMessage: function(){
return this.message.split('').reverse().join('')
}
}
})
app.mount('#app')
<div id="app">
{{reversedMessage}}
</div>
算出プロパティVSメソッド
どっちも同じじゃない?と思うかもしれないが、違う!
methos | computed |
---|---|
メソッド | プロパティ |
呼び出しに()必要 | 呼び出しに()不要 |
getter | getter/setter |
キャッシュなし | キャッシュあり |
監視プロパティ(ウォッチャ)
* 特定のデータまたは、算出プロパティの状態を監視して、変化があったときに登録した処理を自動的に実行できるもの。
- 検索フォームの値が変わったタイミングで自動的にAjaxを行なって結果を一覧表示する。
<div id="app">
<p>{{message}}</p>
<p><input type="text" v-model="message"></p>
</div>
const app = Vue.createApp({
data: () => ({
message: '',
}),
watch: {
message: function(newValue, oldValue){
console.log('new: %s, old:%s', newValue, oldValue)
}
}
})
app.mount('#app')
距離計算
<div id="app">
<p><input type="text" v-model="km"></input>km</p>
<p><input type="text" v-model="m"></input>m</p>
</div>
const app = Vue.createApp({
data: () => ({
m: '0',
km: '0',
}),
watch: {
m: function(value){
this.m = value,
this.km = value / 1000
},
km: function(value){
this.km = value,
this.m = value * 1000
}
}
})
app.mount('#app')
算出プロパティVS監視プロパティ
どちらでも行けるときは、基本的には算出プロパティを使う。
算出プロパティの方がコードをスリムにかける。
deepオプション
ネストされたオブジェクトも監視する。
const app = Vue.createApp({
data: () => ({
colors: [
{ name: "red"},
{ name: "green"},
{ name: "blue"},
]
}),
watch: {
colors: {
handler: function(newValue, oldValue) {
console.log("update!")
},
deep: true
}
},
methods: {
onClick: function(){
this.colors[1].name = "white"
}
}
})
app.mount('#app')
<div id="app">
<ul>
<li v-for="color in colors">{{color.name}}</li>
</ul>
<button @click="onClick()">ボタン</button>
</div>
以下の公式ページもわかりやすいので見てみると良い。
APIを叩く
axiosとdebouncerを使ってAPIを叩く。
<div id="app">
<p><input type="text" v-model="keyword"></p>
<p>{{message}}</p>
<ul>
<li v-for="item in items">{{item.title}}</li>
</ul>
</div>
const app = Vue.createApp({
data: () => ({
items: null,
keyword: "",
message:"",
}),
watch:{
keyword: function(keyword){
this.message = "waiting for you"
this.debouncedGetAnswer()
}
},
mounted: function(){
this.debouncedGetAnswer = _.debounce(this.getAnswer, 1000)
},
methods: {
getAnswer: function () {
if(this.keyword === ""){
this.items = null
return
}
this.message = "Loading..."
const vm = this
const params = { page: 1, per_page: 20, query: this.keyword}
axios.get('https://qiita.com/api/v2/items', {params})
.then(function(response){
vm.items = response.data
})
.catch(function(error){
vm.message = "errorが発生しました。" + error
})
.finally(function(){
vm.message = ""
})
}
}
})
app.mount('#app')
クラスとスタイルのバインディング
v-bind:class(:classは省略記法)
<p>hello!<span v-bind:class="{ large: isLarge}">world!</span></p>
複数のクラスを動的に切り替え
<p>hello!<span :class="{ large: isLarge, 'text-danger': hasError }">world!</span></p>
const app = Vue.createApp({
data: () => ({
isLarge: true,
hasError: true
})
})
app.mount('#app')
プレーンなクラス属性と共存
以下のようにプレーンクラスと共存することも可能
<p>hello!<span class="bg-gray text-blue" :class="{ large: isLarge, 'text-danger': hasError }">world!</span></p>
配列構文によるクラスのデータバインディング
.large {
font-size: 36px;
}
.danger{
color: red;
}
const app = Vue.createApp({
data: () => ({
largeClass: "large",
dangerClass: "danger"
})
})
app.mount('#app')
<div id="app">
<p>hello!<span :class="largeClass">world!</span>
</p>
<p>hello!<span :class="[largeClass, dangerClass]">world!</span>
</p>
</div>
オブジェクトデータの使用
const app = Vue.createApp({
data: () => ({
classObject:{
'large': true,
'danger': true
}
})
})
app.mount('#app')
<div id="app">
<p>hello!<span :class="classObject">world!</span></p>
</div>
クラス条件に三項演算子を使用
三項演算子を使用して、クラス名を指定することもできる。
また、常に適用するクラスと同時に使用することもできる。
<p>hello!<span :class="isLarge ? largeClass : dangerClass">world!</span></p>
<p>hello!<span :class="[isLarge ? largeClass : '', dangerClass]">world!</span></p>
const app = Vue.createApp({
data: () => ({
largeClass:{
'large': true,
'bg-gray': true
},
dangerClass:{
'danger': true
},
isLarge: false
})
})
app.mount('#app')
インラインスタイルのデータバインディングにオブジェクトデータを使用
クラスの適用として、以下のような書き方があるけど、ちょっと面倒。
<p>hello!<span :style="{color: color, fontSize: fontSize + 'px'}">world!</span></p>
const app = Vue.createApp({
data: () => ({
color: 'blue',
fontSize: '30'
})
})
app.mount('#app')
こっちの方が楽でいいよね。
<p>hello!<span :style="classStyle">world!</span></p>
const app = Vue.createApp({
data: () => ({
classStyle:{
color: 'blue',
fontSize: '30px'
}
})
})
app.mount('#app')
条件レンダリング
v-if/v-else
要素の表示と非表示を真偽値で切り替える。
以下のように記述する。
<p v-if="toggle">YES</p>
<p v-else>No</p>
const app = Vue.createApp({
data: () => ({
toggle: true,
})
})
app.mount('#app')
注意点としてv-ifの直後にv-elseがある必要があるそうです。したがって、以下のような書き方をするとエラーは出ませんが、思った通りの挙動にはなりません。
<p v-if="toggle">YES</p>
<p>hogehoge</p>
<p v-else>No</p>
v-else-if
<div id="app">
<input type="text" v-model="color">
<p v-if="color === 'blue' ">
Go
</p>
<p v-else-if="color === 'yellow'">
Caution
</p>
<p v-else-if="color === 'red'">
Stop
</p>
<p v-else>
not red/yellow/blue
</p>
</div>
const app = Vue.createApp({
data: () => ({
color: ''
})
})
app.mount('#app')
v-show
- 要素のdisplay CSSプロパティを切り替えることで、表示、非表示を切り替える。
- trueの場合、cssに'display: none'が割り当てられるため画面に表示されなくなる。
- v-elseやv-else-ifとは連動しない。
<p v-show="toggle">hello</p>
const app = Vue.createApp({
data: () => ({
toggle: true
})
})
app.mount('#app')
v-ifとv-showの違い
v-if | v-show |
---|---|
要素をDOMから削除・追加 | CSS displayプロパティ |
高い切り替えコスト | 高い初期描画コスト |
v-else/v-else-ifが使える | v-else/v-else-ifが使えない |
イベントハンドラ
引数を渡したらイベントは渡らなくなるので、使用する場合は明示的に$event
と渡す必要がある。知らなかった...
const app = Vue.createApp({
data: () => ({
message: ''
}),
methods:{
clickHandler: function($event, message){
this.message = message
console.log($event)
}
}
})
app.mount('#app')
チェックボックス
単体のチェックボックス boolean値
<div id="app">
<label for="checkbox">{{checked}}</label>
<input type="checkbox" id="checkbox" v-model="checked">
</div>
const app = Vue.createApp({
data: () => ({
checked: false
}),
})
app.mount('#app')
複数のチェックボックス 配列
空の配列を用意してー
const app = Vue.createApp({
data: () => ({
colors: []
}),
})
app.mount('#app')
そして、チェックボックスにチェックが入ると、この用意されたからの配列に入っていく。ちなみに、入るのはvalue属性の値。
今回だと、こんな感じ[ "green", "blue", "red" ]
で入っていく。
<div id="app">
<label for="checkbox">red</label>
<input type="checkbox" value="red" id="red" v-model="colors">
<label for="checkbox">blue</label>
<input type="checkbox" value="blue" id="blue" v-model="colors">
<label for="checkbox">green</label>
<input type="checkbox" value="green" id="green" v-model="colors">
{{colors}}
</div>
ラジオボタン 文字列
ラジオボタンは文字列らしい。
空の文字列を用意してあげる。
const app = Vue.createApp({
data: () => ({
color: ''
}),
})
app.mount('#app')
で、バリューに値を定義しておく。チェックされると、文字列としてバリューがcolorのバリューに入る。
<div id="app">
<label for="radio">red</label>
<input type="radio" value="red" id="red" v-model="color">
<label for="radio">blue</label>
<input type="radio" value="blue" id="blue" v-model="color">
<label for="radio">green</label>
<input type="radio" value="green" id="green" v-model="color">
{{color}}
</div>
セレクトボックス 文字列
次にセレクトボックス。これはラジオボタンと同じで文字列らしい。
jsはラジオボックスの使い回し。そのまま。
const app = Vue.createApp({
data: () => ({
color: ''
}),
})
app.mount('#app')
以下のように記述すると、選ばれた色が文字列としてcolorに格納される。
<div id="app">
<select v-model="color">
<option disabled>選択してください。</option>
<option>red</option>
<option>green</option>
<option>blue</option>
</select>
{{color}}
</div>
セレクトボックス複数選択 配列
複数選択の場合は、格納先を配列として用意する。
const app = Vue.createApp({
data: () => ({
color: []
}),
})
app.mount('#app')
<div id="app">
<select v-model="color" multiple>
<option>red</option>
<option>green</option>
<option>blue</option>
</select>
{{color}}
</div>
表示されたセレクトボックスを複数選択すると、 [ "green", "blue" ]
のように格納される。
ディレクティブまとめ
v-text
要素のテキストコンテンツを設定します。
<div v-text="message">This is the message</div>
v-html
要素のHTMLコンテンツを設定します。
<div v-html="html">This is some HTML</div>
v-show
ブール式に基づいて要素を表示または非表示にします。
<div v-show="isVisible">This element is visible</div>
v-if
ブール式に基づいて要素を条件付きでレンダリングします。
<div v-if="condition">This element is rendered if condition is true</div>
v-for
配列を反復処理し、配列内の各アイテムに対して新しい要素をレンダリングします。
<ul>
<li v-for="item in items">
{{ item.name }}
</li>
</ul>
v-on
イベントハンドラを要素にバインドします。
<button v-on="click: handleClick">Click me</button>
v-bind
要素のプロパティをデータプロパティにバインドします。
<input v-bind:value="name" />
v-model
入力要素の値をデータプロパティにバインドします。
<input v-model="name" />
v-cloak
レンダリングの準備ができるまで要素を非表示にします。
<div v-cloak>
This element is hidden until it is rendered
</div>
v-pre
要素にバインドされたときにHTMLがエンコードされるのを防ぎます。
<div v-pre>
This element will not be encoded when it is rendered
</div>
v-once
要素が1回だけレンダリングされるようにします。
<div v-once>
This element will only be rendered once
</div>
v-non-interactive
要素を非インタラクティブにします。
<div v-non-interactive>
This element is non-interactive
</div>
v-focus
要素にフォーカスを当てます。
<input v-focus />
v-placeholder
入力要素のプレースホルダーテキストを設定します。
<input v-placeholder="placeholder" />
v-tabindex
要素のタブインデックスを設定します。
<input v-tabindex="tabindex" />
v-attr
要素の属性を設定します。
<div v-attr:id="id">
This element has an ID of {{ id }}
</div>
v-html-safe
HTMLがエンコードされずにレンダリングされるようにします。
<div v-html-safe="html">
This element will not be encoded when it is rendered
</div>
v-deep
ディレクティブを要素の子孫すべてに適用します。
<ul>
<li v-for="item in items">
<ul v-for="subitem in item.subitems">
{{ subitem.name }}
</ul>
</li>
</ul>
v-model-options
v-modelディレクティブのオプションを指定します。
<input v-model="name" v-model-options="{trim: true}" />
v-slot
名前付きスロットを作成します。
<template v-slot:default>
This is the default slot
</template>
<template v-slot:greeting="{ name }">
Hello, {{ name }}!
</template>
v-memo
キャッシュされたコンピュートプロパティを作成します。
<computed v-memo="getComputedValue">
This computed property is memoized
</computed>
ライフサイクルフック
Composition APIでは、各ライフサイクルに該当する関数が用意されています。
Vue 2(Options API) | Vue 3(Options API) | Vue 3(Composition API) |
---|---|---|
beforeCreate | beforeCreate | - |
created | created | - |
beforeMount | beforeMount | onBeforeMount() |
mounted | mounted | onMounted() |
beforeUpdate | beforeUpdate | onBeforeUpdate() |
updated | updated | onUpdated() |
beforeDestroy | beforeUnmount | onBeforeMounted() |
destroyed | unmounted | onUnmounted() |