vue的雙向綁定原理:Object.defineProperty()
vue實現數據雙向綁定主要是:采用數據劫持結合發布者-訂閱者模式的方式,通過 Object.defineProperty() 來劫持各個屬性的setter,getter,在數據變動時發布消息給訂閱者,觸發相應監聽回調。當把一個普通 Javascript 對象傳給 Vue 實例來作為它的 data 選項時,Vue 將遍歷它的屬性,用 Object.defineProperty() 將它們轉為 getter/setter。用戶看不到 getter/setter,但是在內部它們讓 Vue 追蹤依賴,在屬性被訪問和修改時通知變化。
vue的數據雙向綁定 將MVVM作為數據綁定的入口,整合Observer,Compile和Watcher三者,通過Observer來監聽自己的model的數據變化,通過Compile來解析編譯模板指令(vue中是用來解析 {{}}),最終利用watcher搭起observer和Compile之間的通信橋梁,達到數據變化 —>視圖更新;視圖交互變化(input)—>數據model變更雙向綁定效果。
Vue簡單使用:(差值表達式)
<div id="app"> <h1>{{msg}} {{num+1}}</h1> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el:"#app", data:{ msg:"Hello World", num:1 } }) </script>
也可以寫做:
<div id="app"> <h1 v-text="msg"></h1> </div> <script> var vm = new Vue({ el:'#app', data() { return { msg: 'hello world' } }, }); </script>
還可以寫做:
<div id="app"> <div v-html="msg"></div> </div> <script> var vm = new Vue({ el:'#app', data() { return { msg: '<h1>hello world</h1>' } }, }); </script>
還可以添加js表達式:
<div id="app"> {{msg + ' Joe'}} <div v-text="msg + ' Joe'"></div> <div v-html="msg + ' Joe'"></div> </div> <script> var vm = new Vue({ el:'#app', data() { return { msg: 'hello world' } }, }); </script>
v-bind 簡寫 :
動態地綁定一個或多個特性,或一個組件 prop 到表達式。
<img v-bind:src="imageSrc"> <!-- 縮寫 --> <img :src="imageSrc">
<a v-bind:href="href">百度</a> <a :href="href">百度</a> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el:"#app", data:{ href:"https://www.baidu.com", } }) </script>
<!-- 通過 prop 修飾符綁定 DOM 屬性 --> <div v-bind:text-content.prop="text"></div> <!-- prop 綁定。“prop”必須在 my-component 中聲明。--> <my-component :prop="someThing"></my-component> <!-- 通過 $props 將父組件的 props 一起傳給子組件 --> <child-component v-bind="$props"></child-component>
動態class 可以綁定對象、數組:
對象綁定例子:點擊文字變為紅色,再點擊恢復原來顏色
<style> .activated{color:red} </style> <div id="app"> <div :class="{activated: isActivated}" @click="handleClick">Hello World</div> </div> </script> <script> var app = new Vue({ el:'#app', data() { return { isActivated:false } }, methods: { handleClick() { this.isActivated = !this.isActivated } }, }); </script>
數組綁定例子:
<style> .activated{color:red} </style> <div id="app"> <div :class="[isActivated]" @click="handleClick">Hello World</div> </div> <script> var app = new Vue({ el:'#app', data() { return { isActivated: '' } }, methods: { handleClick() { this.isActivated = this.isActivated === 'activated' ? '' : 'activated' } }, }); </script>
當然也可以直接綁定style,對象例子:
<div id="app"> <div :style="styleObj" @click="handleClick">Hello World</div> </div> <script> var app = new Vue({ el:'#app', data() { return { styleObj: {color:'black'} } }, methods: { handleClick() { this.styleObj.color = this.styleObj.color === 'black' ? 'red' : 'black' } }, }); </script>
數組例子:數組里的對象來實現
<div id="app"> <div :style="[styleObj, {fontSize: '20px'}]" @click="handleClick">Hello World</div> </div> <script> var vm = new Vue({ el:'#app', data() { return { styleObj: {color:'black'} } }, methods: { handleClick() { this.styleObj.color = this.styleObj.color === 'black' ? 'red' : 'black' } }, }); </script>
v-if v-else v-show條件渲染
<div v-if="see">if true</div> <div v-else>if false</div> <div v-show="see">show true</div> <script> var app = new Vue({ el:"#app", data:{ see:false, } }) </script>
v-show為false時只是是隱藏了,相當於display:none。v-show的性能比v-if好。
for循環: ( in )也可以用( of )
<ul> <li v-for="item in person">我是{{item.name}},我{{item.age}}歲了</li> </ul> <ul> <li v-for="item in obj">{{item}}</li> </ul> <script> var app = new Vue({ el:"#app", data:{ person:[ {name:'111',age:'20'}, {name:'222',age:'22'},{name:'333',age:'26'}, ], obj:{name:'444',age:"30"}, //對象循環的是value值 } }) </script>

如果要循環key的話:第二參
<ul> <li v-for="(item, index) in person">{{index+1}}-我是{{item.name}},我{{item.age}}歲了</li> </ul> <ul> <li v-for="(item, name) in obj">{{name}}:{{item}}</li> </ul>
key值:標識,禁止復用。當給元素添加key后,就不會在出現Dom復用。
例子:上面的例子如果添加一個按鈕,點擊按鈕刪除第一條數據,如果不寫key的話,實際刪除的是Dom里的第三條li,因為Dom復用的原因,加上key,就不會出現Dom復用,刪除的就是第一個li
<button @click="person.splice(0,1)">刪除</button> <!--點擊刪除第一條數據--> <ul> <li v-for="(item, index) in person" :key="item.name"> {{index+1}}-我是{{item.name}},我{{item.age}}歲了 </li> </ul>
input例子:
//input也要加key避免復用 <div v-if="see"> 登錄:<input type="text" placeholder="登錄" key="login" /> </div> <div v-else> 注冊:<input type="text" placeholder="注冊" key="register" /> </div> //不過一般input都會綁定v-model監聽 <div v-if="see"> 登錄:<input type="text" placeholder="登錄" v-model="login" /> </div> <div v-else> 注冊:<input type="text" placeholder="注冊" v-model="register" /> </div>
控制v-for循環次數的多種方法:
1、通過html的標簽里面v-if對超出范圍的進行隱藏
<div v-for="(item,index) in courselist" v-if="index < 6" :key='index'></div>
2、通過sclie截取數組的長度控制循環的次數
<div v-for="(item,index) in courselist.slice(0,6)" :key="index"></div>
template模板占位符:
可以幫助我們去包裹一些元素,但是在循環過程中,並不會被真正的渲染到頁面上.
例如:
<template v-for="item of list" :key="item.id"> <div>{{item.title}}</div> <span>{{item.time}}</span> </template>
頁面查看元素會發現:div下面是span,然后還是div和span
對象循環:
<div id="app"> <div v-for="(item, key, index) of userInfo">{{item}} -- {{key}} -- {{index}}</div> </div> <script> var vm = new Vue({ el:'#app', data() { return { userInfo:{ name: 'Joe', age: 28, gender: 'male', salary: 'secret' } } }, }); </script>
set方法
通過Vue.set全局方法給對象里添加值:
控制台輸入:Vue.set(vm.userInfo, "address", "beijing") 回車,然后頁面會變為5項內容.
也可以用實例方法來實現:vm.$set(vm.userInfo, "address", "beijing")
數組上的set方法使用:
<div id="app"> <div v-for="item of userInfo">{{item}}</div> </div> <script> var vm = new Vue({ el:'#app', data() { return { userInfo:[1, 2, 3, 4] } }, }); </script>
在控制台輸入:Vue.set(vm.userInfo, 1, 5),這時頁面會變為1 5 3 4
同樣也可以調用實例方法,vm.$set(vm.userInfo, 2, 6),頁面變為 1 5 6 4
v-on 簡寫@ 用到監聽
//點擊see的值取反,上面條件區域是否顯示 <div> <h1>條件渲染</h1> <div v-if="see">if true</div> <div v-else>if false</div> <div v-show="see">show true</div> </div> <div> <h1>v-on</h1> <button v-on:click="see = !see">click me</button> </div>
復雜的功能可以寫個方法:
<div> <h1>v-on</h1> <button @click="click">click me</button> </div> <script> var app = new Vue({ el:"#app", data:{ see:false, }, methods: { click:function(){ this.see = !this.see; //this指向當前app } }, }) </script>
傳參:
<div> <h1>v-on</h1> <button @click="click('hello')">click me</button> </div> <script> var app = new Vue({ el:"#app", data:{ see:false, }, methods: { click:function(val){ this.see = !this.see; //this指向當前app alert(val); } }, }) </script>
監聽input事件:
<input v-on:input="click" />
<div id="app"> <form action="/abc"> <input type="submit" /> </form> </div>
例子:點擊提交,不希望頁面進行跳轉(阻止from表單的默認行為)
<div id="app"> <form action="/abc" @click="handleClick"> <input type="submit" /> </form> </div> <script> var vm = new Vue({ el:'#app', methods: { handleClick (e) { e.preventDefault(); // 阻止默認行為 } }, }); </script>
現在怎么點擊提交,也不會進行跳轉了。當然還可以簡寫為:@click.prevent
<div id="app"> <!-- <form action="/abc" @click="handleClick"> --> <form action="/abc" @click.prevent> <input type="submit" /> </form> </div> <script> var vm = new Vue({ el:'#app', }); </script>
<div id="app"> <div @click="handleClick"> <div>hello world</div> </div> </div> <script> var vm = new Vue({ el:'#app', methods: { handleClick () { console.log(123) } }, }); </script>
點擊hello world打印出123,但是實際上我們在點擊hello world的時候,經過了一次冒泡,實際上我點擊的元素是div,通過冒泡,外層的div上觸發了 handleClick 事件。
下面我們希望只有當點擊了外層的這個div,才觸發 handleClick 事件;如果是通過冒泡行為冒泡上來的事件,並不執行。就需要用到 @click.self
<div id="app"> <div @click.self="handleClick"> nick <div>hello world</div> </div> </div>
這時候點擊 hello world 控制台不打印出123;當點擊nick時,打印出123.
@click.once :指的是只在div上綁定一次handleClick,當事件觸發過一次后,事件就會自動解綁。
@click.capture :capture 遵循的就不是冒泡,而是捕獲的規則
例子:
<div @click="handleClick"> <div @click="handleClickInner">hello world</div> </div> <script> var vm = new Vue({ el:'#app', methods: { handleClick () { console.log(123) }, handleClickInner () { console.log('inner') } }, }); </script>
點擊hello world后的結果是:先打印出inner,再通過冒泡到外層,再打印出123。
<div @click.capture="handleClick"> <div @click.capture="handleClickInner">hello world3</div> </div>
現在點擊hello world后的結果是:先打印出123,再打印出inner,這時候遵循的是捕獲規則。
按鍵修飾符:
例子:
<div id="app"> <input @keydown="handleInput" /> </div> <script> var vm = new Vue({ el:'#app', methods: { handleInput (e) { console.log(e.target.value) } }, }); </script>
這樣在input框輸入內容就會在控制台打印出來。當我們不希望一輸入就打印,而是當我們輸入完成后按回車的時候在打印到控制台:
<input @keydown.enter="handleInput" />
刷新后,當我們輸入內容時並不會打印,而是輸入完按回車后,才在控制台打印出結果。
當然按鍵修飾符還有很多,比如:tab 就是當tab鍵被點擊的時候對應的事件才會被執行;還有del、esc等等的。
系統修飾符:ctrl alt shift meta
<input @keydown.ctrl="handleInput" /> <!-- .ctrl:輸入完成后,點擊ctrl鍵,輸入的內容才會被打印出來 -->
鼠標按鍵的修飾符:left、right、middle
例子:
<div id="app"> <div @click.right="handleClick">click</div> </div> <script> var vm = new Vue({ el:'#app', methods: { handleClick () { console.log(123) } }, }); </script>
v-model 雙向綁定:
<div><input type="text" v-model="textVal" />{{textVal}}</div> <script> var app = new Vue({ el:"#app", data:{ textVal:"", //初始化 }, methods: { //事件監聽 }, }) </script>
單選:
<div> <input type="radio" value="0" id="a" v-model="radioVal" /><label for="a">A</label> <input type="radio" value="1" id="b" v-model="radioVal" /><label for="b">B</label> <input type="radio" value="2" id="c" v-model="radioVal" /><label for="c">C</label> {{radioVal}} </div> <script> var app = new Vue({ el:"#app", data:{ radioVal:"", }, }) </script>
復選:
<div> <input type="checkbox" value="0" id="aa" v-model="checkboxVal" /><label for="aa">A</label> <input type="checkbox" value="1" id="bb" v-model="checkboxVal" /><label for="bb">B</label> <input type="checkbox" value="2" id="cc" v-model="checkboxVal" /><label for="cc">C</label> {{checkboxVal}} </div> <script> var app = new Vue({ el:"#app", data:{ checkboxVal:[], //初始化數組 } })
下拉選擇框:
<div> <select v-model="selectVal"> <option value="0">A</option> <option value="1">B</option> <option value="2">C</option> <option value="3">D</option> </select> {{selectVal}} </div> <script> var app = new Vue({ el:"#app", data:{ selectVal:0, //初始為0默認選第一 } }) </script>
上面的都可以設置初始值:
data:{ textVal:"111", radioVal:"0", checkboxVal:[0], selectVal:0, }
Vue表單綁定之中的修飾符:lazy、number、trim
例子:lazy
<div id="app"> <input type="text" v-model.lazy="value"/> {{value}} </div> <script> var vm = new Vue({ el:'#app', data () { return { value: '' } } }); </script>
我們輸入內容時,沒有任何變化,當input框失去焦點的時候,輸入的內容才一次性的顯示出來。
例子:number
<div id="app"> <input type="text" v-model="value"/> {{value}} </div> <script> var vm = new Vue({ el:'#app', data () { return { value: '' } }, watch: { value () { console.log(typeof this.value) // 打印出輸入內容的類型 } } }); </script>
我們在輸入框里輸入字母和漢字時,控制台打印出String類型,但是我們輸入123數字時,也打印出String類型。我們想輸入數字類型時怎么辦呢?
<input type="text" v-model.number="value"/>
刷新后,我們輸入abc,輸出的還是String類型,因為它轉化不了numberleix。當輸入123后,打印出來的就是number類型了。
例子:trim 首、尾空格的去除
<div id="app"> <input type="text" v-model.trim="value"/> {{value}} </div>
當我們在輸入框里輸入“ adfa ”時,發現結果還是 adfa ,.trim 把首部和尾部的空格都去除掉了。
計算屬性:computed
計算屬性有緩存機制:當計算屬性所依賴的值沒有改變時,就不會去重新計算。
簡單實例:
<div id="app"> {{fullName}} </div> <script> var vm = new Vue({ el:'#app', data() { return { firstName: 'Joe', lastName: 'Lee', } }, // 計算屬性 computed: { fullName() { return this.firstName + ' ' + this.lastName } }, }); </script>
實例:
<h1>{{msg.split('').reverse().join('')}}</h1> //dlroW olleH 截取反轉拼接 //computed實現 <h1>{{msg}}</h1> <h2>{{reversedMsg}}</h2> <script> var app = new Vue({ el:"#app", data:{ msg:"Hello World", }, computed: { //計算屬性 reversedMsg:function(){ return this.msg.split('').reverse().join(''); } }, }) </script> //也可以通過方法實現 <h1>{{msg}}</h1> <h2>{{reversedMsg()}}</h2> <script> var app = new Vue({ el:"#app", data:{ msg:"Hello World", }, methods: { //事件監聽 reversedMsg:function(){ return this.msg.split('').reverse().join(''); } }, }) </script>
不建議用方法的形式,推薦使用計算屬性。
結果:

例子:展示年齡大於30歲的數據
<ul> <h1>v-for</h1> <li v-for="(item, index) in person" v-if="item.age > 30"> {{index+1}}-我是{{item.name}},我{{item.age}}歲了 </li> </ul> <script> var app = new Vue({ el:"#app", data:{ person:[ {name:'111',age:'20'}, {name:'222',age:'28'},{name:'333',age:'36'}, ], }, }) </script> //官方推薦用計算屬性寫法 <ul> <li v-for="(item, index) in newPerson"> {{index+1}}-我是{{item.name}},我{{item.age}}歲了 </li> </ul> <script> var app = new Vue({ el:"#app", data:{ person:[ {name:'111',age:'20'}, {name:'222',age:'28'},{name:'333',age:'36'}, ], }, computed: { //計算屬性 newPerson:function(){ return this.person.filter(function(item){return item.age > 30}); } }, }) </script>
計算屬性的setter和getter
例子:
<div id="app"> {{fullName}} </div> <script> var vm = new Vue({ el:'#app', data() { return { firstName: 'Joe', lastName: 'Lee' } }, // 計算屬性 computed: { fullName: { get: function(){ return this.firstName + ' ' + this.lastName }, set: function(value){ // console.log(value); var arr = value.split(' '); this.firstName = arr[0]; this.lastName = arr[1]; }, } }, }); </script>
然后在控制台輸入:vm.fullName = 'Tom Wang'
回車后可以看到頁面變為Tom Wang。這個就是計算屬性的set和get。
監聽屬性:watch
//輸入問題時答案顯示waiting,1秒鍾后顯示404 <div> question:<input type="text" placeholder="enter" v-model="question" /><br> answer:{{answer}} </div> <script> var app = new Vue({ el:"#app", data:{ question:"", answer:"no answer", }, watch: { //監聽屬性 question:function(){ this.answer = "waiting"; var _this = this; setTimeout(function(){ _this.answer = "404"; },1000); } }, }) </script>
vue提供的7種數組變異方法:
push、pop、shift刪除第一項、unshift往第一項添加、splice截取操作、sort排序、reverse取反
vue檢測數組變動:
有兩種情況變動的數組,是VUE不能檢測到的,也不會觸發視圖的更新。
- 通過索引直接設置項 例如: vm.items[indexOfItem] = newValue
- 修改數組的長度 vm.items.length = newLength
舉個例子:
var vm = new Vue({ data: { items: ['a', 'b', 'c'] } }) vm.items[1] = 'x' // 不是響應性的 vm.items.length = 2 // 不是響應性的
為了解決第一類問題,以下兩種方式都可以實現和 vm.items[indexOfItem] = newValue 相同的效果,同時也將觸發狀態更新:
// Vue.set Vue.set(vm.items, indexOfItem, newValue) // Array.prototype.splice vm.items.splice(indexOfItem, 1, newValue)
為了解決第二類問題,可以使用 splice:
vm.items.splice(newLength)
生命周期函數:
生命周期函數:就是vue實例在某一個時間點自動執行的函數。
vue有8種生命周期函數:
| 鈎子函數 | 觸發的行為 | 在此階段可以做的事情 |
|---|---|---|
| beforeCreadted | vue實例的掛載元素$el和數據對象data都為undefined,還未初始化。 | 加loading事件 |
| created | vue實例的數據對象data有了,$el還沒有 | 結束loading、請求數據為mounted渲染做准備 |
| beforeMount | vue實例的$el和data都初始化了,但還是虛擬的dom節點,具體的data.filter還未替換。 | .. |
| mounted | vue實例掛載完成,data.filter成功渲染 | 配合路由鈎子使用 |
| beforeUpdate | data更新時觸發 | |
| updated | data更新時觸發 | 數據更新時,做一些處理(此處也可以用watch進行觀測) |
| beforeDestroy | 組件銷毀時觸發 | |
| destroyed | 組件銷毀時觸發,vue實例解除了事件監聽以及和dom的綁定(無響應了),但DOM節點依舊存在 | 組件銷毀時進行提示 |
當vm實例中有template的,就用模板渲染,沒有的話就把el元素,對應的html作為模板被渲染。
<div id="app"></div> <script> var vm = new Vue({ el:'#app', template: '<div>hello world</div>', beforeCreate: function() { console.log('beforeCreate'); }, created() { console.log('created'); }, }); </script> <!--相當於--> <div id="app">hello world</div> <script> var vm = new Vue({ el:'#app', beforeCreate: function() { console.log('beforeCreate'); }, created() { console.log('created'); }, }); </script>
-
在beforeCreate和created鈎子函數之間的生命周期
在這個生命周期之間,進行初始化事件,進行數據的觀測,可以看到在created的時候數據已經和data屬性進行綁定(放在data中的屬性當值發生改變的同時,視圖也會改變)。
注意看下:此時還是沒有el選項 -
created鈎子函數和beforeMount間的生命周期
首先會判斷對象是否有el選項。如果有的話就繼續向下編譯,如果沒有el選項,則停止編譯,也就意味着停止了生命周期,直到在該vue實例上調用vm.$mount(el)。
這之后,觀察到template參數選項對生命周期的影響
(1)如果vue實例對象中有template參數選項,則將其作為模板編譯成render函數。
(2)如果沒有template選項,則將外部HTML作為模板編譯。
(3)template中的模板優先級要高於outer HTML的優先級。
(4)render函數選項的優先級最高。 -
beforeMount和mounted 鈎子函數間的生命周期
給vue實例對象添加$el成員,並且替換掉掛在的DOM元素。 -
mounted
el已經渲染完成並掛載到實例上 -
beforeUpdate鈎子函數和updated鈎子函數間的生命周期
當vue發現data中的數據發生了改變,會觸發對應組件的重新渲染,先后調用beforeUpdate和updated鈎子函數。 -
beforeDestroy和destroyed鈎子函數間的生命周期
beforeDestroy鈎子函數在實例銷毀之前調用。在這一步,實例仍然完全可用。
destroyed鈎子函數在Vue 實例銷毀后調用。調用后,Vue 實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀。
<div id="app"></div> <script> var vm = new Vue({ el:'#app', template: '<div>{{test}}</div>', data() { return { test: 'hello world' } }, beforeCreate: function() { console.log('beforeCreate'); }, created() { console.log('created'); }, beforeMount() { console.log(this.$el); console.log('beforeMount'); }, mounted() { console.log(this.$el); console.log('mounted'); }, beforeDestroy() { console.log('beforeDestroy'); }, destroyed() { console.log('destroyed'); }, beforeUpdate() { console.log('beforeUpdate'); }, updated() { console.log('updated'); }, }); </script>
控制台打印:

組件注冊:
在注冊一個組件的時候,我們始終需要給它一個名字。比如在全局注冊的時候我們已經看到了:
Vue.component('my-component-name', { /* ... */ })
該組件名就是 Vue.component 的第一個參數。
定義組件名的方式有兩種:
使用 kebab-case
當使用 kebab-case (短橫線分隔命名) 定義一個組件時,你也必須在引用這個自定義元素時使用 kebab-case,例如 <my-component-name>。
Vue.component('my-component-name', { /* ... */ })
使用 PascalCase
當使用 PascalCase (首字母大寫命名) 定義一個組件時,你在引用這個自定義元素時兩種命名法都可以使用。也就是說 <my-component-name> 和 <MyComponentName> 都是可接受的。注意,盡管如此,直接在 DOM (即非字符串的模板) 中使用時只有 kebab-case 是有效的。
Vue.component('MyComponentName', { /* ... */ })
全局注冊:
到目前為止,我們只用過 Vue.component 來創建組件:
Vue.component('my-component-name', {
// ... 選項 ...
})
這些組件是全局注冊的。也就是說它們在注冊之后可以用在任何新創建的 Vue 根實例 (new Vue) 的模板中。比如:
Vue.component('component-a', { /* ... */ })
Vue.component('component-b', { /* ... */ })
Vue.component('component-c', { /* ... */ })
new Vue({ el: '#app' })
<div id="app">
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
局部注冊:
可以通過一個普通的 JavaScript 對象來定義組件:
var ComponentA = { /* ... */ } var ComponentB = { /* ... */ } var ComponentC = { /* ... */ }
然后在 components 選項中定義你想要使用的組件:
new Vue({ el: '#app', components: { 'component-a': ComponentA, 'component-b': ComponentB } })
對於 components 對象中的每個屬性來說,其屬性名就是自定義元素的名字,其屬性值就是這個組件的選項對象。
注意局部注冊的組件在其子組件中不可用。例如,如果你希望 ComponentA 在 ComponentB 中可用,則你需要這樣寫:
var ComponentA = { /* ... */ } var ComponentB = { components: { 'component-a': ComponentA }, // ... }
或者如果你通過 Babel 和 webpack 使用 ES2015 模塊,那么代碼看起來更像:
import ComponentA from './ComponentA.vue' export default { components: { ComponentA }, // ... }
用is來解決小bug:
例子:
<div id="app"> <table> <tbody> <!-- 這里直接寫<row></row>會出錯,tr會跑到table外面 --> <tr is="row"></tr> <tr is="row"></tr> <tr is="row"></tr> </tbody> </table> </div> <script> Vue.component('row', { template: '<tr><td>This is</td></tr>' }) var vm = new Vue({ el:'#app', }); </script>
同理,ul、ol、select這些標簽也是一樣用is
在子組件里,定義data的時候,data必須是個函數,而不能是對象
例子:
Vue.component('row', {
data(){
return {
content: 'This is content'
}
},
template: '<tr><td>{{content}}</td></tr>'
})
ref引用:可以用ref來獲取DOM節點
例子:
<!-- 點擊div后控制台輸出div的內容 --> <div id="app"> <div ref="hello" @click="handleClick">hello world</div> </div> <script> var vm = new Vue({ el:'#app', methods: { handleClick() { console.log(this.$refs.hello.innerHTML) } }, }); </script>
組件中的ref引用:獲取子組件的引用
例子:計數器
<div id="app"> <counter ref="one" @change="handleChange"></counter> <counter ref="two" @change="handleChange"></counter> <div>求和:{{total}}</div> </div> <script> Vue.component('counter', { data() { return { number: 0 } }, template: '<div @click="handleClick">{{number}}</div>', methods: { handleClick() { this.number ++ // 當子組件數目發生變化時,向外觸發一個 change 事件 this.$emit('change') } }, }) var vm = new Vue({ el:'#app', data() { return { total: 0 } }, methods: { handleChange() { //console.log(this.$refs.one) this.total = this.$refs.one.number + this.$refs.two.number } }, }); </script>
組件通信:
1、props / $emit
父組件 A 通過 props 的方式向子組件 B 傳遞,B to A 通過在 B 組件中 $emit, A 組件中 v-on 的方式實現。
(1)父組件給子組件傳值:父組件通過屬性的形式向下傳遞數據給子組件,子組件通過props接收。
例子:還是計數器
<div id="app"> <counter :count="0"></counter> <counter :count="1"></counter> </div> <script> var counter = { props: ['count'], template: '<div @click="handleClick">{{count}}</div>', methods: { handleClick() { this.count ++ } } } var vm = new Vue({ el:'#app', components:{ counter: counter }, }); </script>
這時點擊0或者1都可以實現+1的功能,但是控制台會報錯:“vue.js:634 [Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: "count"”
子組件不能修改父組件傳遞過來的參數。
單項數據流:父組件可以通過屬性的形式向子組件傳遞參數,也可以隨便修改,但是子組件絕對不能反過來修改父組件傳遞過來的這個參數。
修改為:
var counter = { props: ['count'], template: '<div @click="handleClick">{{number}}</div>', data() { return { number: this.count } }, methods: { handleClick() { this.number ++ } } }
這時點擊可以實現+1的功能,控制台也不會報錯了。
例子1:購物車地址
//父Home.vue <Nav :NavActiveIndex="activeIndex"></Nav> <script> import Nav from "./Nav.vue"; export default { name: "home", components: { Nav }, data() { return { activeIndex: '/home/buycar', activeIndex2: '1' }; }, </script> //子Nav.vue {{NavActiveIndex}} //home/buycar 實現默認綁定購物車 <el-menu :default-active="NavActiveIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect" background-color="#545c64" text-color="#fff" router active-text-color="#ffd04b"> <script> export default { name: "nav", props:{ NavActiveIndex:String }, }, </script>
(2)子組件向父組件傳值:
通過觸發組件自定義事件的方式來實現給父組件傳值:
例子:繼續上面的計數器功能
<div id="app"> <counter :count="0" @change="handleChange"></counter> <counter :count="1" @change="handleChange"></counter> <div>求和:{{total}}</div> </div> <script> var counter = { props: ['count'], template: '<div @click="handleClick">{{number}}</div>', data() { return { number: this.count // 副本 } }, methods: { handleClick() { // this.number ++; // this.$emit('change', 1) this.number = this.number + 2; this.$emit('change', 2) } } } var vm = new Vue({ el:'#app', components:{ counter: counter }, data() { return { total: 1 } }, methods: { handleChange(step) { this.total += step } }, }); </script>
例子1:購物車地址
//父組件 Home.vue //綁定自定義事件fromNavVal <Nav v-if="NavOpen" :NavActiveIndex="activeIndex" @fromNavVal="fromNavVal"></Nav> <div v-else>折疊{{activeIndex2}}</div> <script> import Nav from "./Nav.vue"; export default { name: "home", components: { Nav }, data() { return { activeIndex: '/home/buycar', activeIndex2: '1', NavOpen: true, //Nav打開折疊 }; }, methods: { fromNavVal(val){ //自定義方法fromNavVal this.activeIndex2 = val; this.NavOpen = false; } } }; </script> //子組件 Nav.vue <button @click="click">折疊Nav</button> <script> export default { name: "nav", props:{ NavActiveIndex:String }, data() { return { activeIndex: '1', activeIndex2: '123' }; }, methods: { handleSelect(key, keyPath) { console.log(key, keyPath); }, click(){ //$emit(事件名,傳出的參數) this.$emit('fromNavVal',this.activeIndex2) } } }; </script>
2、組件參數校驗與非props特性
<div id="app"> <child content="hello world"></child> </div> <script> Vue.component('child', { props:{ content: String, // content類型必須為字符串類型 }, template: '<div>{{content}}</div>', }) var vm = new Vue({ el:'#app', }); </script>
例子:傳遞的參數必須為數字類型
<div id="app"> <child :content="123"></child> </div> <script> Vue.component('child', { props:{ content: Number // content類型必須為數字類型 }, template: '<div>{{content}}</div>', }) var vm = new Vue({ el:'#app', }); </script>
例子:傳遞的參數類型可以為字符串也可以為數字
props:{ content: [String, Number], // content類型可以為字符串類型或者數字類型 },
例子:更復雜的校驗
<div id="app"> <child content="aaaaaa"></child> </div> <script> Vue.component('child', { props:{ content: { type: String, // 類型為字符串 required: false, // true表示:content 這個屬性是必傳的;false 為非必傳 default: 'hello', // 默認值,有傳值的時候不生效 validator: function(value) { return (value.length > 5 ) // content 的內容長度必須大於5 } } }, template: '<div>{{content}}</div>', }) var vm = new Vue({ el:'#app', }); </script>
非Props特性:
是指父組件向子組件傳遞了一個屬性,但是子組件並沒有聲明props接收父組件傳遞過來的內容。並且非props特性的屬性會顯示在DOM中。
例子:
<div id="app"> <child content="aaaaaa"></child> </div> <script> Vue.component('child', { template: '<div>hello</div>', }) var vm = new Vue({ el:'#app', }); </script>
圖:
給組件綁定原生事件: 事件修飾符native 監聽原生的點擊事件
例子:
<div id="app"> <child @click="handleClick"></child> </div> <script> Vue.component('child', { template: '<div @click="handleChild">Child</div>', methods: { handleChild(){ console.log('child chick'); // 觸發原生事件 this.$emit('click'); // 觸發自定義事件 } }, }) var vm = new Vue({ el:'#app', methods: { handleClick() { console.log('click'); } }, }); </script> <!--可以通過事件修飾符native來實現--> <div id="app"> <child @click.native="handleClick"></child> </div> <script> Vue.component('child', { template: '<div>Child</div>', }) var vm = new Vue({ el:'#app', methods: { handleClick() { console.log('click'); } }, }); </script>
3、event bus 事件處理中心(非父子組件間的傳值)
具體實現方式:
var Event = new Vue(); Event.$emit(事件名,數據); Event.$on(事件名,data => {});
例子:點擊上面文字,下面文字變為上面文字的內容;點擊下面文字,上面文字變為下面文字的內容。
<div id="app"> <child content="Joe"></child> <child content="Lee"></child> </div> <script> Vue.prototype.bus = new Vue(); Vue.component('child', { data(){ return { selfContent : this.content // 單向數據流拷貝副本 } }, props: { content: String, }, template: '<div @click="handleClick">{{selfContent}}</div>', methods: { handleClick() { // console.log(this.content); this.bus.$emit('change', this.selfContent); // 向外觸發事件 } }, mounted() { //生命周期鈎子 var that = this; this.bus.$on('change', msg => { // 監聽事件 // console.log(msg); that.selfContent = msg; }) }, }) var vm = new Vue({ el:'#app', }); </script>
例子:實現購物車數量。
先建立一個事件處理中心js文件,bus.js:
import Vue from "vue"; const EventBus = new Vue(); export default EventBus;
在Nav.vue里引入bus.js
<el-menu-item index="/home/buycar">購物車{{buycarCount}}</el-menu-item> <!--4展示購物車數量-->
//1首先在Nav.vue里引入bus.js
<script>
import bus from '@/assets/bus.js';
export default {
name: "nav",
props:{
NavActiveIndex:String
},
data() {
return {
activeIndex: '1',
activeIndex2: '123',
buycarCount: 0, //3購物車數量
};
},
created() {
//2注冊組件
bus.$on("buycarCountChang",(num) =>{ //監聽事件buycarCountChang
this.buycarCount = num;
})
},
}
在buycar.vue里引入bus.js
<script> //1.引入bus.js import bus from '@/assets/bus.js'; export default { 。。。 watch: { //2.監聽list改變 list:{ //list監聽:當key是list,值是函數的時候,是不深度遍歷的,也就是對象改變的時候不監聽,所以用這種寫法 handler:function(){ //handler處理函數 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) bus.$emit("buycarCountChang",count); //購物車數量改變的時候觸發buycarCountChang事件 }, deep:true //true的時候深度監聽 } } }
4、Vue中使用插槽 slot 和作用域插槽 slot-scope
例子:
<div id="app"> <child> <h1>Joe</h1> </child> </div> <script> Vue.component('child', { template: '<div><p>Hello</p><slot></slot></div>', }) var vm = new Vue({ el:'#app', }); </script>
結果:
例子:默認值
<div id="app"> <child> </child> </div> <script> Vue.component('child', { template: '<div><p>Hello</p><slot>默認內容</slot></div>', }) var vm = new Vue({ el:'#app', }); </script>
結果:

例子:具名插槽
<div id="app"> <child> <div class="header" slot="header">header</div> <div class="footer" slot="footer">footer</div> </child> </div> <script> Vue.component('child', { template: `<div> <slot name="header"></slot> <div class="content">content</div> <slot name="footer"></slot> </div>`, }) var vm = new Vue({ el:'#app', }); </script>
結果:

當然也可以有默認值:
<div id="app"> <child> <div class="footer" slot="footer">footer</div> </child> </div> <script> Vue.component('child', { template: `<div> <slot name="header">default header</slot> <div class="content">content</div> <slot name="footer"></slot> </div>`, }) var vm = new Vue({ el:'#app', }); </script>
作用域插槽:
當子組件循環或某一部分的dom結構應該由外部傳遞進來的時候,我們要用作用域插槽。使用作用域插槽,子組件可以向父組件的作用域插槽里傳遞數據,父組件如果想接收這個數據,必須在外層使用template模版占位符,同時通過slot-scope對應的屬性名字,來接收你傳遞過來的數據。
例子:
<div id="app"> <child> <template slot-scope="scope"> <p>{{scope.item}}</p> </template> </child> </div> <script> Vue.component('child', { data() { return { list:[1, 2, 3, 4] } }, template: `<div> <slot v-for="item in list" :item=item>{{item}}</slot> </div>`, }) var vm = new Vue({ el:'#app', }); </script>
上面代碼,傳遞一個item過來,在父組件的作用域插槽里面,就可以接收到這個item,就可以使用它了。
動態組件與v-once指令
動態組件:就是會根據is里面數據的變化,自動的加載不同的組件
例子:交替顯示
<div id="app"> <child-one v-if="type === 'child-one'"></child-one> <child-two v-else></child-two> <button @click="changeBtn">change</button> </div> <script> Vue.component('child-one', { template: '<div>child-one</div>' }) Vue.component('child-two', { template: '<div>child-two</div>' }) var vm = new Vue({ el:'#app', data() { return { type: 'child-one' } }, methods: { changeBtn() { this.type = this.type === 'child-one' ? 'child-two' : 'child-one'; } }, }); </script> <!--使用動態組件實現一樣的效果--> <div id="app"> <component :is="type"></component> <button @click="changeBtn">change</button> </div>
v-once指令:
當組件的內容每次都一樣時,可以使用v-once指令,當第1個組件第一次被渲染的時候,因為組件上有個v-once指令,所以直接就放在內容里了,當切換后,第2個組件被渲染的時候,也會被放在內存里,當我們再點擊切換的時候,這時候並不需要重新創建第1個組件,而是從內存里直接拿出以前的第1個組件。這樣會節省性能。
例子:
<div id="app"> <child-one v-if="type === 'child-one'"></child-one> <child-two v-else></child-two> <button @click="changeBtn">change</button> </div> <script> Vue.component('child-one', { template: '<div v-once>child-one</div>' }) Vue.component('child-two', { template: '<div v-once>child-two</div>' }) var vm = new Vue({ el:'#app', data() { return { type: 'child-one' } }, methods: { changeBtn() { this.type = this.type === 'child-one' ? 'child-two' : 'child-one'; } }, }); </script>
5、VueX:狀態管理
Vuex 是一個專為 Vue.js 應用程序開發的狀態管理模式。它采用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
核心模塊:State、Getters、Mutations、Actions、Module
(1)、State:
(2)、Getters:
Getter相當於vue中的computed計算屬性,getter 的返回值會根據它的依賴被緩存起來,且只有當它的依賴值發生了改變才會被重新計算,這里我們可以通過定義vuex的Getter來獲取,Getters 可以用於監聽、state中的值的變化,返回計算后的結果。
(3)、Mutations:
更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。但是,mutation只允許同步函數。
(4)、Actions:
官方並不建議我們直接去修改store里面的值,而是讓我們去提交一個actions,在actions中提交mutation再去修改狀態值。可以異步操作
Action 類似於 mutation,不同在於:
- Action 提交的是 mutation,而不是直接變更狀態。
- Action 可以包含任意異步操作。
(5)、Module
Vuex 允許我們將store 分割成模塊(module)。每個模塊擁有自己的 state、mutation、action、getter、甚至是嵌套子模塊——從上至下進行同樣方式的分割。
簡要介紹各模塊在流程中的功能:
- Vue Components:Vue 組件。HTML 頁面上,負責接收用戶操作等交互行為,執行 dispatch 方法觸發對應 action 進行回應。
- dispatch:操作行為觸發方法,是唯一能執行 action 的方法。
- actions:操作行為處理模塊,由組件中的
$store.dispatch('action 名稱', data1)來觸發。然后由 commit()來觸發 mutation 的調用 , 間接更新 state。負責處理 Vue Components 接收到的所有交互行為。包含同步/異步操作,支持多個同名方法,按照注冊的順序依次觸發。向后台 API 請求的操作就在這個模塊中進行,包括觸發其他 action 以及提交 mutation 的操作。該模塊提供了 Promise 的封裝,以支持 action 的鏈式觸發。 - commit:狀態改變提交操作方法。對 mutation 進行提交,是唯一能執行 mutation 的方法。
- mutations:狀態改變操作方法,由 actions 中的
commit('mutation 名稱')來觸發。是 Vuex 修改 state 的唯一推薦方法。該方法只能進行同步操作,且方法名只能全局唯一。操作之中會有一些 hook 暴露出來,以進行 state 的監控等。 - state:頁面狀態管理容器對象。集中存儲 Vue components 中 data 對象的零散數據,全局唯一,以進行統一的狀態管理。頁面顯示所需的數據從該對象中進行讀取,利用 Vue 的細粒度數據響應機制來進行高效的狀態更新。
- getters:state 對象讀取方法。圖中沒有單獨列出該模塊,應該被包含在了 render 中,Vue Components 通過該方法讀取全局 state 對象。
例子:購物車數量改為用VueX管理
在store/index.js里
export default new Vuex.Store({ state: { buycarCount: 0 //1初始化buycarCount數量0 }, mutations: { //2定義同步方法改變count changbuycarCount(state, num) { state.buycarCount = num; } }, actions: {}, modules: {} });
Nav.vue:
<el-menu-item index="/home/buycar">購物車{{this.$store.state.buycarCount}}</el-menu-item>
<!--2直接獲取值-->
<script>
//import bus from '@/assets/bus.js'; 不用了注釋掉
export default {
。。。
1.list不需要監聽事件了,注釋掉
created() { //生命周期
// bus.$on("buycarCountChang",(num) =>{ //監聽事件buycarCountChang 購物車數量改變的時候觸發事件
// this.buycarCount = num;
// })
},
};
</script>
buycar.vue:
<script> //import bus from '@/assets/bus.js'; 同樣注釋掉 export default { 。。。 watch: { list:{ //監聽list handler:function(){ //handler處理函數 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //購物車數量改變時觸發buycarCountChang事件 //1注釋上面代碼改為下面代碼 this.$store.commit("changbuycarCount", count); //購物車數量改變時觸發changbuycarCount方法 }, deep:true //true的時候深度監聽 } } }; </script>
OK,這樣就實現了VueX改變接收購物車數量。
當然如果覺得{{this.$store.state.buycarCount}}這種獲取數據太繁瑣,可以用映射函數來實現。
Nav.vue:
<el-menu-item index="/home/buycar">購物車{{buycarCount}}</el-menu-item>
<!--3直接使用{{buycarCount}}-->
<script>
//import bus from '@/assets/bus.js';
import {mapState} from "vuex"; //1映射函數 結構賦值 映射到計算屬性
export default {
name: "nav",
props:{
NavActiveIndex:String
},
data() {
return {
activeIndex: '1',
activeIndex2: '123',
//buycarCount: 0, //4注釋掉上面定義的購物車數量 重名沖突
};
},
computed: { //計算屬性
...mapState(['buycarCount']), //2用...展開運算符把buycarCount展開在資源屬性里
},
};
</script>
好了,用映射函數同樣實現VueX改變接收購物車數量的功能。
那么我們的mutations也是有映射函數的,在buycar.vue里也用映射函數的方式來實現:
<script> //import bus from '@/assets/bus.js'; import {mapMutations} from 'vuex' //1映射函數 映射到方法 export default { 。。。 methods: { //2...展開運算符,傳一個數組映射成同名的方法changbuycarCount ...mapMutations([ "changbuycarCount" ]), 。。。 }, watch: { //watch是vue的監聽,一旦監聽對象有變化就會執行相應操作 list:{ //監聽list 當key是list,值是函數的時候,是不深度遍歷的,也就是說對象改變的時候不監聽,所以用這種寫法 handler:function(){ //handler處理函數 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //購物車數量改變時觸發buycarCountChang事件 //this.$store.commit("changbuycarCount", count); //購物車數量改變時觸發changbuycarCount方法 //3注釋上面代碼改為下面代碼 this.changbuycarCount(count); //this實例changbuycarCount方法,把count傳過來 }, deep:true //true的時候深度監聽 } } }; </script>
OK,功能同樣實現。
下面action來實現異步操作:
在store/index.js里定義方法:
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: { buycarCount: 0 //初始化0 }, mutations: { //定義同步方法改變count changbuycarCount(state, num) { state.buycarCount = num; } }, actions: { //異步改變 第1個參數是上下文 第2個接收調用傳參 asyncchangbuycarCount(content, num) { //一般實現ajax,這里就不調用接口,實現一個定時器功能 setTimeout(() => { content.commit("changbuycarCount", num); //content對象的commit方法,來觸發mutations }, 1000); } }, modules: {} });
修改buycar.vue:
<script> //import bus from '@/assets/bus.js'; import {mapMutations} from 'vuex' //映射函數 映射到方法 export default { 。。。 watch: { //watch是vue的監聽,一旦監聽對象有變化就會執行相應操作 list:{ //監聽list 當key是list,值是函數的時候,是不深度遍歷的,也就是說對象改變的時候不監聽,所以用這種寫法 handler:function(){ //handler處理函數 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //購物車數量改變時觸發buycarCountChang事件 //this.$store.commit("changbuycarCount", count); //購物車數量改變時觸發changbuycarCount方法 //this.changbuycarCount(count); //this實例changbuycarCount方法,把count傳過來 //1注釋上面代碼改為下面代碼 this.$store.dispatch("asyncchangbuycarCount", count); }, deep:true //true的時候深度監聽 } } }; </script>
現在實現操作,1秒鍾后改變購物車數量。
action同樣有映射函數,直接修改buycar.vue:
<script> //import bus from '@/assets/bus.js'; import {mapMutations, mapActions} from 'vuex' //1映射函數 添加mapActions export default { 。。。 methods: { ...mapMutations(["changbuycarCount"]), //...展開運算符,傳一個數組映射成同名的方法changbuycarCount ...mapActions(["asyncchangbuycarCount"]), //2展開 asyncchangbuycarCount }, watch: { //watch是vue的監聽,一旦監聽對象有變化就會執行相應操作 list:{ //監聽list 當key是list,值是函數的時候,是不深度遍歷的,也就是說對象改變的時候不監聽,所以用這種寫法 handler:function(){ //handler處理函數 let count = 0; this.list.forEach( item => { count += parseInt(item.count); //item.count累加 }) //bus.$emit("buycarCountChang",count); //購物車數量改變時觸發buycarCountChang事件 //this.$store.commit("changbuycarCount", count); //購物車數量改變時觸發changbuycarCount方法 //this.changbuycarCount(count); //this實例changbuycarCount方法,把count傳過來 //this.$store.dispatch("asyncchangbuycarCount", count); //異步的改變asyncchangbuycarCount //3注釋掉上面代碼改為下面代碼 this.asyncchangbuycarCount(count); }, deep:true //true的時候深度監聽 } } }; </script>
同樣實現1秒鍾后改變購物車數量。
下面用getters來實現在導航Nav上顯示名字的功能。
在store/index.js里定義:
import Vue from "vue"; import Vuex from "vuex"; Vue.use(Vuex); export default new Vuex.Store({ state: { buycarCount: 0, //初始化0 //1建立用戶信息 userinfo: { name: "latte", age: 20 } }, mutations: { //定義同步方法改變count changbuycarCount(state, num) { state.buycarCount = num; } }, actions: { //異步改變 第1個參數是上下文 第2個接收調用傳參 asyncchangbuycarCount(content, num) { //一般實現ajax,這里就不調用接口,實現一個定時器功能 setTimeout(() => { content.commit("changbuycarCount", num); //content對象的commit方法,來觸發mutations }, 1000); } }, //2.getters返回計算屬性 getters: { userName(state) { return state.userinfo.name; //返回用戶name } }, modules: {} });
修改Nav.vue:
<div>{{userName}}</div>
<!--3展示在nav-->
<script>
//import bus from '@/assets/bus.js';
import {mapState, mapGetters} from "vuex"; //1直接用映射函數 添加mapGetters
export default {
。。。
computed: { //計算屬性
...mapState(['buycarCount']), //用...展開運算符把buycarCount展開在資源屬性里
...mapGetters(['userName']), //2...展開
},
。。。
};
</script>
效果實現了。
注意: 官方建議如果您不打算開發大型單頁應用,使用 Vuex 可能是繁瑣冗余的。確實是如此——如果您的應用夠簡單,您最好不要使用 Vuex。當然,還是需要您自己來決定。
Vuex 與 localStorage:
let defaultCity = "上海" try { // 用戶關閉了本地存儲功能,此時在外層加個try...catch if (!defaultCity){ defaultCity = JSON.parse(window.localStorage.getItem('defaultCity')) } }catch(e){} export default new Vuex.Store({ state: { city: defaultCity }, mutations: { changeCity(state, city) { state.city = city try { window.localStorage.setItem('defaultCity', JSON.stringify(state.city)); // 數據改變的時候把數據拷貝一份保存到localStorage里面 } catch (e) { } } } })
這里需要注意的是:由於 vuex 里,我們保存的狀態,都是數組,而 localStorage 只支持字符串,所以需要用 JSON 轉換:
JSON.stringify(state.subscribeList); // array -> string JSON.parse(window.localStorage.getItem("subscribeList")); // string -> array
6、$attrs/$listeners
簡介:多級組件嵌套需要傳遞數據時,通常使用的方法是通過 vuex。但如果僅僅是傳遞數據,而不做中間處理,使用 vuex 處理,未免有點大材小用。為此 Vue2.4 版本提供了另一種方法----
$attrs/
$listeners
$attrs:包含了父作用域中不被 prop 所識別 (且獲取) 的特性綁定 (class 和 style 除外)。當一個組件沒有聲明任何 prop 時,這里會包含所有父作用域的綁定 (class 和 style 除外),並且可以通過 v-bind="$attrs" 傳入內部組件。通常配合 interitAttrs 選項一起使用。$listeners:包含了父作用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它可以通過 v-on="$listeners" 傳入內部組件
// index.vue <template> <div> <h2>浪里行舟</h2> <child-com1 :foo="foo" :boo="boo" :coo="coo" :doo="doo" title="前端工匠" ></child-com1> </div> </template> <script> const childCom1 = () => import("./childCom1.vue"); export default { components: { childCom1 }, data() { return { foo: "Javascript", boo: "Html", coo: "CSS", doo: "Vue" }; } }; </script>
// childCom1.vue <template class="border"> <div> <p>foo: {{ foo }}</p> <p>childCom1的$attrs: {{ $attrs }}</p> <child-com2 v-bind="$attrs"></child-com2> </div> </template> <script> const childCom2 = () => import("./childCom2.vue"); export default { components: { childCom2 }, inheritAttrs: false, // 可以關閉自動掛載到組件根元素上的沒有在props聲明的屬性 props: { foo: String // foo作為props屬性綁定 }, created() { console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" } } }; </script>
// childCom2.vue <template> <div class="border"> <p>boo: {{ boo }}</p> <p>childCom2: {{ $attrs }}</p> <child-com3 v-bind="$attrs"></child-com3> </div> </template> <script> const childCom3 = () => import("./childCom3.vue"); export default { components: { childCom3 }, inheritAttrs: false, props: { boo: String }, created() { console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" } } }; </script>
// childCom3.vue <template> <div class="border"> <p>childCom3: {{ $attrs }}</p> </div> </template> <script> export default { props: { coo: String, title: String } }; </script>
效果:

如上圖所示$attrs表示沒有繼承數據的對象,格式為{屬性名:屬性值}。Vue2.4 提供了$attrs , $listeners 來傳遞數據與事件,跨級組件之間的通訊變得更簡單。
簡單來說:$attrs與$listeners 是兩個對象,$attrs 里存放的是父組件中綁定的非 Props 屬性,$listeners里存放的是父組件中綁定的非原生事件。
7、provide/inject
簡介:Vue2.2.0 新增 API,這對選項需要一起使用, 以允許一個祖先組件向其所有子孫后代注入一個依賴,不論組件層次有多深,並在起上下游關系成立的時間里始終生效。一言而蔽之:祖先組件中通過 provider 來提供變量,然后在子孫組件中通過 inject 來注入變量。// A.vue export default { provide: { name: '浪里行舟' } }
// B.vue export default { inject: ['name'], mounted () { console.log(this.name); // 浪里行舟 } }
可以看到,在 A.vue 里,我們設置了一個 provide: name,值為 浪里行舟,它的作用就是將 name 這個變量提供給它的所有子組件。而在 B.vue 中,通過 inject 注入了從 A 組件中提供的 name 變量,那么在組件 B 中,就可以直接通過 this.name 訪問這個變量了,它的值也是 浪里行舟。這就是 provide / inject API 最核心的用法。
需要注意的是:provide 和 inject 綁定並不是可響應的。這是刻意為之的。然而,如果你傳入了一個可監聽的對象,那么其對象的屬性還是可響應的----vue 官方文檔
所以,上面 A.vue 的 name 如果改變了,B.vue 的 this.name 是不會改變的,仍然是 浪里行舟。
provide 與 inject 怎么實現數據響應式:
一般來說,有兩種辦法:
- provide 祖先組件的實例,然后在子孫組件中注入依賴,這樣就可以在子孫組件中直接修改祖先組件的實例的屬性,不過這種方法有個缺點就是這個實例上掛載很多沒有必要的東西比如 props,methods
- 使用 2.6 最新 API Vue.observable 優化響應式 provide(推薦)
// A 組件 <div> <h1>A 組件</h1> <button @click="() => changeColor()">改變color</button> <ChildrenB /> <ChildrenC /> </div> ...... data() { return { color: "blue" }; }, // provide() { // return { // theme: { // color: this.color //這種方式綁定的數據並不是可響應的 // } // 即A組件的color變化后,組件D、E、F不會跟着變 // }; // }, provide() { return { theme: this//方法一:提供祖先組件的實例 }; }, methods: { changeColor(color) { if (color) { this.color = color; } else { this.color = this.color === "blue" ? "red" : "blue"; } } } // 方法二:使用2.6最新API Vue.observable 優化響應式 provide // provide() { // this.theme = Vue.observable({ // color: "blue" // }); // return { // theme: this.theme // }; // }, // methods: { // changeColor(color) { // if (color) { // this.theme.color = color; // } else { // this.theme.color = this.theme.color === "blue" ? "red" : "blue"; // } // } // }
// F 組件 <template functional> <div class="border2"> <h3 :style="{ color: injections.theme.color }">F 組件</h3> </div> </template> <script> export default { inject: { theme: { //函數式組件取值不一樣 default: () => ({}) } } }; </script>
雖說 provide 和 inject 主要為高階插件/組件庫提供用例,但如果你能在業務中熟練運用,可以達到事半功倍的效果!
8、$parent / $children與 ref
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實例$parent/$children:訪問父 / 子實例
需要注意的是:這兩種都是直接得到組件實例,使用后可以直接調用組件的方法或訪問數據。我們先來看個用 ref來訪問組件的例子:
// component-a 子組件 export default { data () { return { title: 'Vue.js' } }, methods: { sayHello () { window.alert('Hello'); } } }
// 父組件 <template> <component-a ref="comA"></component-a> </template> <script> export default { mounted () { const comA = this.$refs.comA; console.log(comA.title); // Vue.js comA.sayHello(); // 彈窗 } } </script>
不過,這兩種方法的弊端是,無法在跨級或兄弟間通信。
// parent.vue <component-a></component-a> <component-b></component-b> <component-b></component-b>
總結
常見使用場景可以分為三類:
- 父子通信:
父向子傳遞數據是通過 props,子向父是通過 events($emit);通過父鏈 / 子鏈也可以通信($parent/$children);ref 也可以訪問組件實例;provide / inject API;$attrs/$listeners - 兄弟通信:
Bus;Vuex - 跨級通信:
Bus;Vuex;provide / inject API、$attrs/$listeners
購物車
事件修飾符native
Vue 中的動畫特效
transition 標簽的使用方法:
主要用於 v-show, v-if 或 router-view 的進出場動畫
語法:
<transition name="name" > <div v-show="show" ></div> <div v-if="show" ></div> <router-view/> </transition> <style> // 定義進入前與離開后狀態 .name-enter, .name-leave-to { ... } // 定義離開前與進入后狀態 .name-leave, .name-enter-to { ... } // 定義進出過程 .name-enter-active, .name-leave-active { transition: all .5s } </style>
1. fade 淡化進出
例子:
<style> /* .fade-enter { opacity: 0; } .fade-enter-active { transition: opacity 3s } */ .v-enter, .v-leave-to { opacity: 0; } .v-enter-active, .v-leave-active { transition: opacity 2s } </style> <div id="app"> <!-- <transition name="fade"> <div v-if="show">Hello World</div> </transition> --> <!-- 如果不寫name,那么樣式默認的前綴是v- --> <transition> <div v-if="show">Hello World</div> </transition> <button @click="handleClick">切換</button> </div>
2. scale 縮放進出
.scale-enter, .scale-leave-to { transform: scale(0) } .scale-leave, .scale-enter-to { transform: scale(1) } .scale-enter-active, .scale-leave-active { transition: all .2s }
3. left 左側進出 (通常用於左側邊欄)
.left-enter, .left-leave-to { transform: translate3d(-100%, 0, 0) } .left-leave, .left-enter-to { transform: translate3d(0, 0, 0) } .left-enter-active, .left-leave-active { transition: all .2s }
4. right 右側進出 (通常用於右側邊欄)
.right-enter, .right-leave-to { transform: translate3d(100%, 0, 0) } .right-leave, .right-enter-to { transform: translate3d(0, 0, 0) } .right-enter-active, .right-leave-active { transition: all .2s }
5. top 頂部進出 (通常用於提示彈窗)
.top-enter, .top-leave-to { transform: translate3d(0, -100%, 0) } .top-leave, .top-enter-to { transform: translate3d(0, 0, 0) } .top-enter-active, .top-leave-active { transition: all .2s }
Vue中使用animate.css庫
例子:
<style> /* css3動畫 */ @keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.5); } 100% { transform: scale(1); } } .fade-enter-active { transform-origin: left center; animation: bounce-in 1s; } .fade-leave-active { transform-origin: left center; animation: bounce-in 1s reverse; /* 離開的時候反向執行 */ } </style> <div id="app"> <transition name="fade" > <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
點擊按鈕后會有一個放大縮小的效果。
也可以自定義名字:還是上面的例子
<style> /* css3動畫 */ @keyframes bounce-in { 0% { transform: scale(0); } 50% { transform: scale(1.5); } 100% { transform: scale(1); } } .active { transform-origin: left center; animation: bounce-in 1s; } .leave { transform-origin: left center; animation: bounce-in 1s reverse; /* 離開的時候反向執行 */ } </style> <div id="app"> <transition name="fade" enter-active-class="active" leave-active-class="leave"> <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
效果完全是一樣的。
通過上面例子就可以使用animate.css庫了 https://daneden.github.io/animate.css/
首先需要把animate.css庫下載下來,當然也可以直接引用cdn的連接。
然后必須使用自定義class這種形式,還有class里必須有一個animated這個類,同時在添加你需要的動畫效果的類名。
例子:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css"> <div id="app"> <transition name="fade" enter-active-class="animated swing" leave-active-class="animated shake"> <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
這樣復雜的動畫就不需要寫了,直接引用animate庫就行了。
繼續上面那個例子來說,點擊toggle按鈕時會有動畫,但是我們刷新頁面的時候沒有,下面我們把第一次顯示時也加上動畫。
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css"> <div id="app"> <transition name="fade" appear enter-active-class="animated swing" leave-active-class="animated shake" appear-active-class="animated swing" > <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
Vue中同時使用過渡和動畫
例子:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css"> <style> .fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active { transition: opacity 3s; } </style> <div id="app"> <transition name="fade" appear enter-active-class="animated swing fade-enter-active" leave-active-class="animated shake fade-leave-active" appear-active-class="animated swing" > <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
效果及既有過渡也有動畫效果了。
我們在過渡設置的時間是3s,查看animate庫里的樣式animated設置動畫時間的卻是1s。這種情況下,我們可以進行手動設置:
<transition type="transition" name="fade" appear enter-active-class="animated swing fade-enter-active" leave-active-class="animated shake fade-leave-active" appear-active-class="animated swing" > <div v-show="show">Hello World</div> </transition>
還可以自定義動畫時長:
例子:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/3.7.2/animate.min.css"> <style> .fade-enter, .fade-leave-to { opacity: 0; } .fade-enter-active, .fade-leave-active { transition: opacity 3s; } </style> <div id="app"> <transition :duration="5000" name="fade" appear enter-active-class="animated swing fade-enter-active" leave-active-class="animated shake fade-leave-active" appear-active-class="animated swing" > <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
打開控制台發現:動畫3s結束后樣式依然存在,5s后樣式才會隱藏。
動畫還可以設置的復雜一點,入場、出場分別設置,出場3秒,入場5秒 :duration="{enter: 5000,leave: 3000}"
例子:
<div id="app"> <transition :duration="{enter: 5000,leave: 3000}" name="fade" appear enter-active-class="animated swing fade-enter-active" leave-active-class="animated shake fade-leave-active" appear-active-class="animated swing" > <div v-show="show">Hello World</div> </transition>
Vue中Js的動畫鈎子
例子:
<div id="app"> <transition name="fade" @before-enter="handleBeforeEnter" @enter="handleEnter" @after-enter='handleAfterEnter'> <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show }, // 當元素從隱藏到顯示的時候會觸發 handleBeforeEnter(el) { el.style.color = 'red' }, // 當before-enter被觸發結束后,下步運行動畫效果的時候,動畫效果寫在enter這個鈎子對應的回調函數里handleEnter handleEnter(el, done) { setTimeout(() => { el.style.color = 'green' // done() }, 2000) setTimeout(() => { done() // 動畫結束 }, 4000) }, handleAfterEnter(el) { el.style.color = 'black' } }, }); </script>
當然還有出場動畫對應的Js鈎子:before-leave、leave、after-leave。
Js常用的動畫庫Velocity.js
http://www.velocityjs.org/
例子:
<div id="app"> <transition name="fade" @before-enter="handleBeforeEnter" @enter="handleEnter" @after-enter='handleAfterEnter'> <div v-show="show">Hello World</div> </transition> <button @click="handleClick">toggle</button> </div> <script src="https://cdn.bootcss.com/velocity/2.0.5/velocity.min.js"></script> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show }, // 當元素從隱藏到顯示的時候會觸發 handleBeforeEnter(el) { el.style.opacity = 0; }, // 當before-enter被觸發結束后,下步運行動畫效果的時候,動畫效果寫在enter這個鈎子對應的回調函數里handleEnter handleEnter(el, done) { Velocity(el, { opacity: 1 }, { duration: 1000, complete: done }) // 動畫從opacity:0 到 opacity:1 耗時1秒,當Velocity執行完這個動畫后,complete這個屬性對應的內容會被自動執行,也就是說done這個回調函數會被執行 }, handleAfterEnter(el) { console.log('動畫結束'); } }, }); </script>
可以看到效果一樣。
Vue中多個元素或組件的過渡動畫
例子:多個元素的過渡動畫
<style> .v-enter, .v-leave-to { opacity: 0; } .v-enter-active, .v-leavev-active { transition: opacity 2s; } </style> <div id="app"> <transition mode="in-out"> <!-- in-out 表示先進入后隱藏;out-in 表示先隱藏后顯示--> <!--dom不進行復用,加key值--> <div v-if="show" key="hello">Hello World</div> <div v-else key="bye">Bye World</div> </transition> <button @click="handleClick">toggle</button> </div> <script> var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show }, }, }); </script>
效果是bye world先顯示,然后hello world隱藏。
例子:多個組件的過渡動畫
<style> .v-enter, .v-leave-to { opacity: 0; } .v-enter-active, .v-leavev-active { transition: opacity 2s; } </style> <div id="app"> <transition mode="in-out"> <!-- in-out 表示先進入后隱藏;out-in 表示先隱藏后顯示--> <child v-if="show"></child> <child-one v-else></child-one> </transition> <button @click="handleClick">toggle</button> </div> <script> Vue.component('child', { template: '<div>child</div>' }) Vue.component('child-one', { template: '<div>child-one</div>' }) var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show }, }, }); </script>
效果和上面是一樣的。
例子:通過動態組件,實現多個組件的過渡動畫
<style> .v-enter, .v-leave-to { opacity: 0; } .v-enter-active, .v-leavev-active { transition: opacity 2s; } </style> <div id="app"> <transition mode="in-out"> <!-- in-out 表示先進入后隱藏;out-in 表示先隱藏后顯示--> <component :is="type"></component> </transition> <button @click="handleClick">toggle</button> </div> <script> Vue.component('child', { template: '<div>child</div>' }) Vue.component('child-one', { template: '<div>child-one</div>' }) var vm = new Vue({ el:'#app', data() { return { type: 'child' } }, methods: { handleClick() { this.type = this.type === 'child' ? 'child-one' : 'child' }, }, }); </script>
當然效果還是一樣的。
Vue中的列表過渡動畫
例子:
<style> .v-enter, .v-leave-to { opacity: 0; } .v-enter-active, .v-leave-active { transition: opacity 2s; } </style> <div id="app"> <transition-group> <div v-for="item of list" :key="item.id">{{item.id}}--{{item.title}}</div> </transition-group> <button @click="handleBtn">Add</button> </div> <script> var count = 0; var vm = new Vue({ el:'#app', data() { return { list: [] } }, methods: { handleBtn() { this.list.push({ id: count++, title: 'hello world' }) } }, }); </script>
通過transition-group實現列表過渡動畫。
Vue中的動畫封裝
通過動畫的封裝來實現復用。
例子:
<style> .v-enter, .v-leave-to { opacity: 0; } .v-enter-active, .v-leave-active { transition: opacity 2s; } </style> <div id="app"> <fade :show="show"> <div>Hello World</div> </fade> <fade :show="show"> <h1>Hello World</h1> </fade> <button @click="handleClick">toggle</button> </div> <script> Vue.component('fade', { props: ['show'], template: ` <transition> <slot v-if="show"></slot> </transition> ` }) var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
這就實現了一個動畫的封裝,每次使用fade組件就可以了
下面把樣式也一起封裝,不用css動畫,使用js動畫:
<div id="app"> <fade :show="show"> <div>Hello World</div> </fade> <fade :show="show"> <h1>Hello World</h1> </fade> <button @click="handleClick">toggle</button> </div> <script> Vue.component('fade', { props: ['show'], template: ` <transition @before-enter="handleBeforeEnter" @enter="handleEnter"> <slot v-if="show"></slot> </transition> `, methods: { handleBeforeEnter(el) { el.style.color = 'red' }, handleEnter(el, done) { setTimeout(() => { el.style.color = 'green' done() },2000) } } }) var vm = new Vue({ el:'#app', data() { return { show: true } }, methods: { handleClick() { this.show = !this.show } }, }); </script>
效果:進入的時候先是紅色,2秒后變為綠色。
