###### tags: `Veu.js` # Vue.js ## Vue.js 運作從 MVVM 框架說起! MVVM全名為 Model-View-ViewModel M 代表 Model(存放資料) V 代表 View(畫面顯示) VM 稱作 ViewModel(資料與畫面互動) ![](https://i.imgur.com/MFZesWe.png) 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> ![](https://i.imgur.com/QnFTH6x.png) ```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", }) ```