###### tags: `Veu.js`
# Vue.js
## Vue.js 運作從 MVVM 框架說起!
MVVM全名為 Model-View-ViewModel
M 代表 Model(存放資料)
V 代表 View(畫面顯示)
VM 稱作 ViewModel(資料與畫面互動)

View和 Model會藉由 ViewModel 來進行聯繫, ViewModel 透過雙向資料綁定來聯繫畫面與資料間的傳遞。
畫面改變時,會改變資料的狀態,資料狀態改變時,畫面也會跟著更動。
<br>
<br>
## New Vue
1. 引入 Vue.js
2. 建立一個變數,並且 new 一個 Vue 物件
3. 建立el (element)並指定範圍
4. data 內存放變數資料
```htmlmixed=
<!-- 1. 引入 Vue.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.2/vue.js"></script>
<div id="app">
<h1>{{message}}</h1> <!--與new vue裡的message做綁定-->
<input type="text" v-model="message"> <!--也可以做雙向綁定-->
<p>{{score *2 +5}}</p>//也可以運算
</div>
```
```javascript=
// 2.new 一個 Vue 物件
new Vue({
el:'#app',//3.與標籤做綁定,不能綁body
data:{ //4.變數都放這邊
message: 'Hello VueJS!',
score: 80,
}
})
```
<br><br><br>
## v-text
除了用{{}},還可以透過v-text綁定DOM元素
```htmlmixed=
<div id="app">
<p v-text="message"></p>
</div>
```
```javascript=
var app = new Vue({
el:'#app',//與標籤做綁定,不能綁body
data:{ //變數都放這邊
message: 'Hello VueJS!',
}
})
```
</br></br>
## v-html
將標籤插入dom,類似javascript的innerHTML
```htmlmixed=
<div id="app">
//v-html 綁定data裡的img
<div v-html="img"></div>
</div>
```
```javascript=
new Vue({
el: "#app",
data: {
//img裡必須放的是html標籤的字串
img: "<img src='https://i.imgur.com/iVSMcsv.png'>"
}
});
```
</br></br>
## V-show<br>
可決定DOM元素是否顯示,使用的技術是display:none<br>
用法: 再標籤上加v-show=""data裡面需要綁定true or false
```htmlmixed=
<div id="app">
<p v-show="loading">載入</p>
</div>
```
```javascript=
var app = new Vue({
el:'#app',//與標籤做綁定,不能綁body
data:{ //變數都放這邊
loading: true,
}
})
```
</br></br>
## v-if
HTML上的判斷式<br>
用法: 再標籤上加v-if=""
```htmlmixed=
<div id="app">
<h1>輸入你的分數</h1>
<input type="text" v-model="score"> //與score雙向綁定
<p v-if="score > 70">Pass !</p>
<p v-else>Not Pass !</p>
</div>
```
```javascript=
var app = new Vue({
el:'#app',//與標籤做綁定,不能綁body
data:{ //變數都放這邊
score: 0,
}
})
```
</br></br>
## v-for
在HTML上跑回圈,動態產生DOM元素<br>
用法: 再標籤上加v-for=""
```htmlmixed=
<div id="app">
<ul>
//v-for裡格式 索引值 in 陣列,標籤裡用大括號包索引值
<li v-for="color in colors">{{color}}</li>
</ul>
<ol>
<li v-for="member in classmate">{{member.name}}-{{member.age}}</li>
</ol>
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
colors: ['red','orange','yellow','green','blue','indigo','violet'],
classmate: [{name:'Anan',age:16},{name:'Bella',age:18},{name:'Cathy',age:15},{name:'Dale',age:23}],
}
})
```
</br></br>
## 進階用法 v-if & v-for
v-if 與 v-for 搭配使用
在第一個v-if上加上 v-for
```htmlmixed=
<p v-for="peopel in classmate" v-if="peopel.age > 18">{{peopel.name}}年紀大於18</p>
<p v-else-if="peopel.age < 18" >{{peopel.name}}年紀小於18</p>
<p v-else>{{peopel.name}}剛好18</p>
```
```javascript=
javascript 延續上一個 v-for 的 new vue
```
## v-bind
HTML上的屬性值,透過v-bind從Vue取得 ex:class、id。<br>
用法: 再屬性值上加 v-bind:
```htmlmixed=
<div id="app">
<img v-bind:src="imgURL">
<input type="text" v-bind:placeholder="message"> //v-bind可以縮寫成":"
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
message: 'Hello VueJS!',
imgURL: '../img/img.png',
}
})
```
</br></br>
## v-on、methods
再HTML上綁定監聽事件<br>
用法: v-on:click="function"
```htmlmixed=
<div id="app">
<ul>
<li v-for="member in classmate" v-on:click="showData(member.name)">{{member.name}}</li>
<li v-for="member in classmate" @click="showData(member.name)">{{member.name}}</li>
</ul>
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
classmate: [{name:'Anan',age:16},{name:'Bella',age:18},{name:'Cathy',age:15},{name:'Dale',age:23}],
},
methods:{//裡面都是放function
showData(data){
alert(data);
}
},
})
```
</br></br>
## this
this 通常在JavaScript裡通常只的是外層物件,所以再Vue function裡 通常指的是Vue這個物件
```htmlmixed=
<div id="app">
<h1 @click="showThis()">showThis!!!</h1>
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
message: 'Hello VueJS!',
},
methods:{
showThis(){
alert(this);// 再這邊alert出來會是物件
alert(this.message); // Hello VueJS!
alert(this.$data); // Hello VueJS!
},
},
})
```
## computed
函數也可以放這裡! 但是放在這裡的函數不能傳參數,一定要有傳回值(return value)
```htmlmixed=
<div id="app">
<h1>現在時間{{now}}</h1>
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
height: 0,
weight: 0,
},
methods:{
},
computed:{//不能傳參數,一定要有傳回值
now(){
let today = new Date();
return today.toTimeString();
},
calculate(){
let BMI = this.weight / Math.pow((this.height / 100), 2);
return BMI.toFixed(2);
},
},
})
```
</br></br>
## v-bind綁定class
透過v-bind 動態綁定class
```css=
CSS
.theButton{
background: #23c423;
border: 0px ;
color: #fff;
border-radius: 5px;
padding: 2px 5px;
}
.change{
background: #888;
}
```
```htmlmixed=
<div id="app"> <!--HTML DOM template-->
<input v-model="message">
//:class 的btnClass從computed來
<button v-bind:class="btnClass">Click me!</button>
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
message:'',
},
methods:{
},
computed:{//不能傳參數,一定要有傳回值
btnClass(){
// 方法1.回傳字串
if(this.message == 0){
return 'theButton change';
}else{
return 'theButton',
}
//方法2.回傳物件
if(this.message == 0){
return {
theButton:true,
change:true,
};
}else{
return {
theButton:true,
change:false,
};
}
//方法3.回傳物件"精簡版"
return{
theButton:true,
change:this.message == 0,
},
},
})
```
<br><br>
## toggle class
主要透過vue data布林值來控制class
```css=
.red{
background-color: red;
}
.blue{
background-color: blue;
}
a{
display: block;
width: 100px;
height: 100px;
border: 1px solid;
margin-top: 50px;
}
```
```htmlmixed=
<div id="app">
//@click 主要反轉布零值
<button @click="add_blue = !add_blue">toggle</button>
//以下會看見兩個class,仔細看會發現一個是原生的,一個是從vue來的
<a class="red" :class="{blue : add_blue}"></a>
</div>
```
```javascript=
new Vue({
el: '#app', //el: document.getElementById('app'),
data: { //變數都放這裡
add_blue: false
},
});
```
<br><br>
## v-bind綁定style
透過v-bind 動態綁定、修改style的值
```htmlmixed=
<div id="app"> <!--HTML DOM template-->
<p>
<button @click="shrink">縮小</button>
<button @click="zoom">放大</button>
<div :style="textChange">Lorem ipsum dolor sit amet.</div>
</p>
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
size: 16,
},
methods:{
shrink(){
this.size -=2;
},
zoom(){
this.size +=2;
},
},
computed:{//不能傳參數,一定要有傳回值
textChange(){
return{
fontSize:`${this.size}px`,
color:'blue',
textDecoration: 'underline',
}
};
}
})
```
## 表單處理
多個選單選扭綁定
```htmlmixed=
<div id="app"> <!--HTML DOM template-->
<input type="radio" v-model="gender" value="male">Male
<input type="radio" v-model="gender" value="female">Female
<input type="radio" v-model="gender" value="others">Others
</div>
```
```javascript=
var app = new Vue({
el:'#app',
data:{
gender:'male',//一開始預設選擇會是male
},
methods:{
},
computed:{//不能傳參數,一定要有傳回值
}
})
```
## 表單處理-多選
將多選鈕的值,透過v-model放進陣列後雙向綁定渲染到Dom
```htmlmixed=
<div id="app">
//v-model=""綁定interest,若勾選 會把資料送進綁定interest裡
<input type="checkbox" v-model="interest" value="read">閱讀
<input type="checkbox" v-model="interest" value="sleep">睡覺
<input type="checkbox" v-model="interest" value="game">電動
<input type="checkbox" v-model="interest" value="diving">潛水
<p>{{interest}}</p>
<input type="checkbox" v-model="agree">同意
</div>
```
```javascript=
new Vue({ //Vue instance(實例)
el: '#app',
data: {
interest: [],
agree: false,
},
methods: {
},
computed: {
},
});
```
## 表單處理-下拉式選單
使用v-model 、 v-for來產生日期下拉式選單
```htmlmixed=
<select v-model="selectedYear">
<option v-for="year in years">{{year}}</option>
</select>
<select v-model="selectedMonth">
<option v-for="month in 12">{{month}}</option>
</select>
<select v-model="selectedDate">
<option v-for="day in 31">{{day}}</option>
</select>
```
```javascript=
new Vue({
el: '#app',
data: {
years: [2016,2017,2018,2019,2020,2021,2022], //3
selectedYear: new Date().getFullYear(),
selectedMonth: new Date().getMonth() + 1,
selectedDate: new Date().getDate(),
},
});
```
表單處理-檔案
```htmlembedded=
<div id="app">
<input type="file" @change="selectedFile">
<p>{{image}}</p>
<img :src="image">
</div>
```
```javascript=
new Vue({ //Vue instance(實例)
el: '#app',
data: {
image: '',
},
methods: {
selectedFile(e){
let file = e.target.files[0];
let readFile = new FileReader();
readFile.readAsDataURL(file);
readFile.addEventListener('load',this.loadImage);
},
loadImage(e){
this.image = e.target.result;
},
},
});
```
## v-model 修飾符
lazy: blur時才觸發事件
number: parseInt() 將字串轉成整數值 ex 20px ===> 20
但1234.567 ===> 1234.567
但aaa123bb ===> aaa123bb
trim: 清除兩側空白
```htmlmixed=
<div id="app"> <!--HTML DOM template-->
Message: <input v-model="message">
<h1>{{message}}</h1>
Message: <input v-model.lazy="message">
<h1>{{message}}</h1>
Number: <input v-model.number="num">
<h1>{{num}}</h1>
Name: <input v-model.trim="name">
<h1>{{name}}</h1>
</div>
```
```javascript=
new Vue({ //Vue instance(實例)
el: '#app',
data: {
message: '',
num: 0,
name: '',
},
methods: {},
});
```
</br></br>
## 生命週期
如同生物一般, Vue 的實體物件從建立、掛載、更新,到銷毀移除,這一連串的過程,我們將它稱作生命週期。 在這個過程中, Vue.js 提供了開發者在這些週期階段做對應處理的 callback function, 這些 callback function 我們就稱它叫生命週期的 Hooks function。</br>

```htmlmixed=
<div id="app"> <!--HTML DOM template-->
<button @click="count++"> +1 </button>
<button id="deleteButton"> Delete </button>
<h1>{{count}}</h1>
</div>
```
```javascript=
let vm = new Vue({ //Vue instance(實例)
el: '#app',
data: {
count: 0,
},
methods: {
},
beforeCreate() {//整個New Vue未產生之前
//這時候this.count 還未出生
console.log(`beforeCreate() --> count: ${this.count}`);
},
created() { //New Vue產生,通常ajax都會掛在這邊
//此時count 已經出世
console.log(`created() --> count: ${this.count}`);
//但$el還沒出生
console.log(`created() --> $el: ${this.$el}`);
},
beforeMount() { //mount 掛載前
//此時$el出生拉 !
console.log(`beforeMount() --> $el: ${this.$el}`);
},
mounted() { //已將new vue掛載到dom
console.log(`mounted() --> $el: ${this.$el}`);
},
beforeUpdate() {//dom元素更新之後,會依序跑beforeUpdate、updated
console.log(`beforeUpdate() --> count: ${this.count}`);
},
updated() {//dom元素更新之後,會依序跑beforeUpdate、updated
console.log(`updated() --> count: ${this.count}`);
},
beforeDestroy() { //銷燬前
console.log(`beforeDestroy() --> count: ${this.count}`);
},
destroyed() { //new vue 銷燬
console.log(`destroyed() --> count: ${this.count}`);
},
});
```
</br></br>
## 偵聽器Watch
watch主要用來監聽data與computed變化
```htmlmixed=
<div id="app"> <!--HTML DOM template-->
<input v-model.lazy="num">
<h2>{{numSquare}}</h2>
</div>
```
```javascript=
let vm = new Vue({ //Vue instance(實例)
el: '#app',
data: {
num: 0,
message: '',
},
methods: {},
computed: {
numSquare(){
return Math.pow(this.num, 2);
},
},
watch: { //偵聽器:主要用來偵聽data和computed的變化
// 1.宣告成方法
// num(newNum,oldNum){
// console.log(`num: ${oldNum} ---> ${newNum}`);
// },
numSquare(newValue,oldValue){
console.log(`numSquare: ${oldValue} ---> ${newValue}`);
},
message(newMsg,oldMsg){
console.log(`message: ${oldMsg} ---> ${newMsg}`);
},
// 2.宣告成物件
num: {
handler(newNum,oldNum){ //handler是內定的function name
console.log(`num: ${oldNum} ---> ${newNum}`);
},
immediate: true, //false by default,當Vue instance一建立立刻執行watch
deep: true, //false by default,見下一個程式
},
},
});
```
</br></br>
## 使用v-for來操作陣列、物件
```htmlmixed=
<div id='app'>
<button @click="run">button</button>
<ul>
// arr第一種寫法
<li v-for='value in array'>{{value}}</li>
// arr第二種寫法,()可以在取到index
<li v-for='(value,index) in array'>index: {{index}} / value: {{vaule}}</li>
// obj第一種寫法
<li v-for="vlaue in obj">{{value}}</li>
// obj第二種寫法,()可以取到key
<li v-for="(value , key) in obj">key: {{key}} / value: {{value}}</li>
</ul>
</div>
```
```javascript=
new Vue({
el:'#app',
data:{
array:[1,2,3,4,5,6,7,8,9],
obj:{
x: 5,
y: 10,
z: 15,
},
},
methods:{
run(){
//push
this.array.push(this.array.length + 1)
//filter
this.array = this.array.filter(function(data){
return data % 2 == 0 ;
})
//filter Arrow function
this.array = this.array.filter(data => data % 2 ==0)
//vue增加屬性、屬性值 必須用$set
this.$set(this.obj,'A',20)
},
},
})
```
## component組件
把重複、共用的結構拆開來,避免一樣的事情重複做
```htmlmixed=
<div id="app">
//此處並不是真正的html標籤,而是VUE讀到後渲染上來
<my-component></my-component>
</div>
```
```javascript=
//註冊一個component,括號裡第一個參數是組件名稱,第二個就放data...,除了el 其他東西都可以放
Vue.component('my-component',{
template:'<div>{{text}}</div>',
data(){
return{
text:'組件內的data必須是function,return物件',
}
}
})
//組件必須先產生,才能夠掛載上去new vue,若new vue先產生
//會產生錯誤
new Vue({
el:'#app',
data:{},
})
```
</br></br>
## 動態切換組件
再button上監聽@click事件,動態切換組件
```htmlmixed=
<div id="app">
<button @click="content="list">List</button>
<button @click="content="apply">Apply</button>
<button @click="content="date">Date</button>
//透過 component元素,去指定其屬性is進行v-bind
<component :is="content"></component>
</div>
```
```javascript=
Vue.component('list',{
template:`
<ul>
<li>HTML</li>
<li>CSS</li>
<li>JavaScript</li>
<li>jQuery</li>
<li>HTML5</li>
<li>VueJS</li>
</ul>
`
})
Vue.component('apply',{
template:`
<form>
<textarea></textarea>
<button>Submit</button>
</form>
`
})
Vue.component('date',{
template: `
<p>
<input type="checkbox"> Morning
<input type="checkbox"> Afternoon
<input type="checkbox"> Evening
</p>
`,
});
let vm = new Vue({
el: '#app',
data: {
content: 'list',
},
});
```
</br></br>
## Keep Alive
當我們切換組件時,並操作dom元素後切換組件時,會發現dom上的資料被清空了
只要透過keep-alive把我們的組件包起來,切換組件時就可以保留當時的資料啦 !!
```htmlmixed=
<div id="app">
<button @click="content="list"> List </button>
<button @click="content="apply"> Apply </button>
<button @click="content="date"> Date </button>
<keep-alive>
<component :is="content"></component>
</keep-alive>
</div>
```
</br></br>
## 資料從父層(New Vue)傳進子組件
當子組件的資料必須向赴層索取,必須透過props
```htmlmixed=
<div id="app">
//方法一
<my-component message="Hello" text="VueJS!"></my-component>
//方法二 使用複合字
<my-component the-message="Hello" the-text="VueJS!"></my-component>
</div>
```
```javascript=
Vue.component("my-component",{
//使用props接收上層傳下的資料 子組件的tamplate才能使用
//方法一
props:['message','text'],
template:'<h1>{{message}} {{text}}</h1>',
// 方法二 在template裡 必須使用camel case接收,不然會壞掉
props:['the-message','the-text'],
template:'<h1>{{theMessage}} {{theText}}</h1>',
})
new Vue({
el:"#app",
data:{
},
})
```
</br></br>
## v-for動態綁定props - 陣列
使用v-for操作data裡的陣列,透過props傳到子組件
```htmlmixed=
<div id="app">
//:the-text 是自定義的屬性
<post v-for="message in messages" :the-text="message"></post>
</div>
```
```javascript=
let vm = new Vue({
el:'#app',
data:{
messages: ['Hello', 'Props','World','!'],
},
components:{
post:{
props:['the-text'],
template:`<h1>{{theText}}</h1>`,
}
},
})
```
## v-for動態綁定props 陣列-物件
使用v-for操作data裡的陣列中的物件,透過props傳到子組件
```htmlmixed=
<div id="app">
//:the-text 是自定義的屬性
<post v-for="text in messageObject" :the-text="text.message" :the-name="text.owner"></post>
</div>
```
```javascript=
let vm = new Vue({
el:'#app',
data:{
messageObject: [
{message: 'Hello',name: 'Andy'},
{message: 'Props',name: 'Bella'},
{message: 'World',name: 'Carl'},
],
},
components:{
post:{
props:['the-text','the-name'],
template:`<h1>{{theName}} // {{theText}}</h1>`,
}
},
})
```
</br></br>
## v-for動態綁定props 陣列-物件 (進階用法)
```htmlmixed=
<div id="app">
//進階用法的v-bind不能夠偷懶使用":",直接v-bind='text'
<post v-for="text in messageObject" v-bind="text"/>
</div>
```
```javascript=
let vm = new Vue({
el:'#app',
data:{
messageObject: [
{message: 'Hello',name: 'Andy'},
{message: 'Props',name: 'Bella'},
{message: 'World',name: 'Carl'},
],
},
components:{
post:{
//使用進階用法,可直接使用陣列物件的key props下來
props:['message','name'],
template:`<h1>{{message}}//{{name}}</h1>`,
}
}
})
```
</br></br>
## 使用$emit來傳送資料到父組件
父組件若需要向子組件拿取資料,必須透過$emit
```htmlmixed=
<div id="app">
//這邊使用aaa來定義一個事件,透過這個事件來呼叫父組件的function
<my-button @aaa="Parent"></my-button>
</div>
```
```javascript=
Vue.component("my-button",{
template:'<button @click="click">Click Me !</button>',
methods:{
click(){
//使用$emit()來跟aaa做綁定,綁定後才會知道Parent是什麼東西
this.$emit('aaa');
//帶參數
this.$emit('aaa',100);
},
},
})
new Vue({
el:"#app",
methods:{
Parent(x){
//無參數版
alert('Parent');
//帶參數版
alert(x);
},
},
})
```
</br></br>
## 子組件之間的溝通
若子組件還要互相傳遞資料、透過props $emit就顯得非常麻煩
所以透過event bus可以幫我們在子組件互相傳遞資料
```htmlmixed=
<div id="app">
<my-button></my-button>
<my-data></my-data>
</div>
```
```javascript=
//一開始必須let一個變數、並賦予他一個new vue
//這邊已bus來舉例
let bus = new Vue();
//透過這個組件的btn click+1 並把資料傳遞到另一個子組件
Vue.component('my-button',{
template:"<button @click='click'>+1</button>",
data(){
return{
count:0,
}
},
methods: {
click(){
this.count ++;
//count ++後,自定一個aaa事件,並把count傳遞出去
bus.$emit('aaa',this.count)
},
},
})
Vue.component('my-data',{
data(){
return{
num:0,
}
},
template:"<h1>{{num}}</h1>",
mounted() {
//在生命週期的mounted 建立一個監聽aaa事件
//監聽到aaa事件後,執行後面的functuon
bus.$on('aaa', (x) => this.num = x);
},
})
new Vue({
el:"#app",
})
```