一、v-model
1、v-model的含義
v-model就是vue的雙向綁定的指令,能將頁面上控件輸入的值同步更新到相關綁定的data屬性,也會在更新data綁定屬性時候,更新頁面上輸入控件的值。
2、v-model的基礎用法
(1)v-model 指令在表單 <input>、<textarea> 及 <select> 元素上創(chuàng)建雙向數(shù)據(jù)綁定。它會根據(jù)控件類型自動選取正確的方法來更新元素。
(2)v-model 本質上不過是語法糖。它負責監(jiān)聽用戶的輸入事件以更新數(shù)據(jù),并對一些極端場景進行一些特殊處理。
(3)v-model 在內(nèi)部為不同的輸入元素使用不同的 property 并拋出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段將 value 作為 prop 并將 change 作為事件。
3、使用示例
當你在input輸入框中輸入內(nèi)容的時候,上面p標簽里面的數(shù)據(jù)實時發(fā)生變化(實際上是name和age兩個數(shù)據(jù)發(fā)生了響應式變化)。相反改變代碼中的name和age值,input輸入框中值也會實時變化。
其實現(xiàn)原理如下:
<div id="app">
<p>姓名:<input :value="name" @input="updateName">{{name}}</p>
<!-- v-model 其實就是v-bind: 和 v-on: 的語法糖 -->
<p>年齡:<input :value="age" @input="updateAge">{{age}}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
name: '張三',
age: 20,
},
methods: {
updateName(e) {
this.name = e.target.value;
},
updateAge(e){
this.age = e.target.value;
}
},
})
</script>
實現(xiàn)效果如下:

實現(xiàn)原理:
①首先input輸入框通過屬性綁定:value="name"&:value="age"得到響應數(shù)據(jù)name&age.
②定義兩個函數(shù),通過e.target得到input框中的value值。
③最后通過input輸入框@input事件監(jiān)聽,綁定兩個函數(shù)(updateName,updateAge),將input框中的value值傳給name&age。
vue中的v-model能夠實現(xiàn)數(shù)據(jù)的雙向綁定,也是vue的最突出的優(yōu)勢。
v-model實際上是v-bind: 和 v-on:的語法糖。它的實現(xiàn)原理主要包括屬性綁定和事件監(jiān)聽兩部分。
具體使用如下:
<div id="app">
<p>姓名:<input v-model="name">{{name}}</p>
<!-- v-model 其實就是v-bind: 和 v-on: 的語法糖 -->
<p>年齡:<input v-model="age">{{age}}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
name: '張三',
age: 20,
},
})
</script>
實現(xiàn)效果與v-bind: + v-on:相同:

二、sync修飾符
在有些情況下,我們可能需要對一個 prop 進行“雙向綁定”。但真正的雙向綁定會帶來維護上的問題,因為子組件可以變更父組件,且在父組件和子組件兩側都沒有明顯的變更來源。
當我們需要在某個標簽中綁定多個屬性時,就選擇使用.sync修飾符。
.sync修飾符的約定:
① 屬性綁定必須是xx.sync
② 自定義事件必須是update:xx格式
③ 采用xx.sync修飾符,可以省略update:xx對應的事件綁定
使用方法如下:
<div id="app">
<div>
衣服:{{yf}},褲子:{{kz}},鞋子:{{xz}}
</div>
<hr>
<!-- 綁定屬性是,采用xx.sync修飾符,可以省略update:xx對應的事件綁定 -->
<!-- 約定1:屬性綁定必須是xx.sync -->
<b-counter :yf.sync="yf" :kz.sync="kz" :xz.sync="xz"></b-counter>
</div>
<script>
Vue.config.productionTip = false
Vue.component('b-counter', {
template: `
<div>
<div class='conter'>
<div class='label'>衣服</div>
<div class='btns'>
<button @click='yfCount--' class='btn'>-</button>
<input type='text' readonly class='txt' :value='yfCount'>
<button @click='yfCount++' class='btn'>+</button>
</div>
</div>
<div class='conter'>
<div class='label'>褲子</div>
<div class='btns'>
<button @click='kzCount--' class='btn'>-</button>
<input type='text' readonly class='txt' :value='kzCount'>
<button @click='kzCount++' class='btn'>+</button>
</div>
</div><div class='conter'>
<div class='label'>鞋子</div>
<div class='btns'>
<button @click='xzCount--' class='btn'>-</button>
<input type='text' readonly class='txt' :value='xzCount'>
<button @click='xzCount++' class='btn'>+</button>
</div>
</div>
</div>`,
props: ['yf', 'kz', 'xz'],
data() {
return {
yfCount: this.yf,
kzCount: this.kz,
xzCount: this.xz,
}
},
// 監(jiān)聽器
watch:{
yfCount(val){
// 約定2:自定義事件必須是update:xx
this.$emit('update:yf',val)
},
kzCount(val){
this.$emit('update:kz',val)
},
xzCount(val){
this.$emit('update:xz',val)
},
}
})
new Vue({
el: '#app',
data: {
// 衣服數(shù)量
yf: 5,
// 褲子數(shù)量
kz: 5,
// 鞋子數(shù)量
xz: 5
}
})
</script>
三、插槽
插槽的含義
插槽就是子組件中的提供給父組件使用的一個占位符,用<slot></slot> 表示,父組件可以在這個占位符中填充任何模板代碼,如 HTML、組件等,填充的內(nèi)容會替換子組件的<slot></slot>標簽。
1、匿名插槽
匿名插槽,我們又可以叫它單個插槽或者默認插槽。與具名插槽相對,它不需要設置name屬性。(它隱藏的name屬性為default。)
示例:
① 在vue子組件中定義一個匿名插槽
<script>
Vue.config.productionTip = false
Vue.component('b-box', {
template: `
<div class="box">
<div class="item">
<h2>插槽</h2>
<slot></slot>
</div>
</div>
`
})
new Vue({
el: '#app',
})
</script>
② 在頁面中使用子組件標簽,并寫入內(nèi)容
<div id="app">
<b-box>
<!-- 如果沒有slot插槽,這里的內(nèi)容將不會顯示 -->
<div>我是匿名插槽</div>
</b-box>
</div>
效果如圖:

2、具名插槽
插槽有一個特殊的屬性:name,這個屬性可以用來定義多個插槽。
在向具名插槽提供內(nèi)容的時候,我們可以在一個 <template> 元素上使用 v-slot 指令,并以 v-slot 的參數(shù)的形式提供其名稱。
v-slot:的簡寫為#
使用方法如下:
<div id="app">
<b-box>
<template v-slot:slot1>
<div>我是插槽1</div>
</template>
<template v-slot:slot2>
<div>我是插槽2</div>
</template>
<template v-slot:slot3>
<div>我是插槽3</div>
</template>
</b-box>
</div>
<script>
Vue.config.productionTip = false
Vue.component('b-box', {
template: `
<div class="box">
<div class="item">
<h2>插槽1</h2>
<slot name="slot1"></slot>
</div>
<div class="item">
<h2>插槽2</h2>
<slot name="slot2"></slot>
</div>
<div class="item">
<h2>插槽3</h2>
<slot name="slot3"></slot>
</div>
</div>
`
})
new Vue({
el: '#app',
})
</script>
實現(xiàn)效果如下:
3、作用域插槽
作用域插槽其實就是可以傳遞數(shù)據(jù)的插槽。子組件中的一些數(shù)據(jù)想在父組件中使用,必須通過規(guī)定的方法來傳遞。
作用域插槽必須是具名插槽,在作用域插槽上可以通過v-bind:綁定屬性,綁定的屬性,通過指定的作用域變量是接收 。
綁定在元素上的 屬性被稱為插槽 prop?,F(xiàn)在在父級作用域中,我們可以使用帶值的 v-slot 來定義我們提供的插槽 prop 的名字:
//default可以省略,簡寫為v-slot=" ",slotProps是自定義的名字,
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
具體使用如下:
<div id="app">
<!-- 作用域插槽必須是具名插槽,在作用域插槽上可以通過v-bind:綁定屬性,綁定的屬性,通過指定的作用域變量是接收 -->
<b-box>
<template #list="scope">
<button @click="priceDown(scope.list,scope.index)">降價</button>
<button @click="priceUp(scope.list,scope.index)">加價</button>
<button @click="scope.list.splice(scope.index,1)">刪除</button>
</template>
</b-box>
</div>
<script>
Vue.config.productionTip = false
Vue.component('b-box', {
template: `
<div>
<ul>
<li v-for="(item,index) in list" :key="index">
<span>{{item.id}}--{{item.name}}--{{item.price}}</span>
<slot name="list" v-bind:index="index" v-bind:list="list"></slot>
</li>
</ul>
</div>
`,
data() {
return {
list: [
{
id: 1001,
name: '蘋果手機',
price: 6799
},
{
id: 1002,
name: '華為手機',
price: 5999
},
{
id: 1003,
name: '小米手機',
price: 1799
},
{
id: 1004,
name: '紅米手機',
price: 3499
},
]
}
},
})
new Vue({
el: '#app',
methods: {
priceDown(list,index){
list[index].price-=1000
},
priceUp(list,index){
list[index].price+=1000
},
},
})
</script>
四、混入
混入 (mixin) 提供了一種非常靈活的方式,來分發(fā) Vue 組件中的可復用功能。一個混入對象可以包含任意組件選項。當組件使用混入對象時,所有混入對象的選項將被“混合”進入該組件本身的選項。
1、選項合并
當組件和混入對象含有同名選項時,這些選項將以恰當?shù)姆绞竭M行“合并”。
比如,數(shù)據(jù)對象在內(nèi)部會進行遞歸合并,并在發(fā)生沖突時以組件數(shù)據(jù)優(yōu)先。
var mixin = {
data: function () {
return {
message: 'hello',
foo: 'abc'
}
}
}
new Vue({
mixins: [mixin],
data: function () {
return {
message: 'goodbye',
bar: 'def'
}
},
created: function () {
console.log(this.$data)
// => { message: "goodbye", foo: "abc", bar: "def" }
}
})
同名鉤子函數(shù)將合并為一個數(shù)組,因此都將被調(diào)用。另外,混入對象的鉤子將在組件自身鉤子之前調(diào)用。
var mixin = {
created: function () {
console.log('混入對象的鉤子被調(diào)用')
}
}
new Vue({
mixins: [mixin],
created: function () {
console.log('組件鉤子被調(diào)用')
}
})
// => "混入對象的鉤子被調(diào)用"
// => "組件鉤子被調(diào)用"
值為對象的選項,例如 methods、components 和 directives,將被合并為同一個對象。兩個對象鍵名沖突時,取組件對象的鍵值對。
var mixin = {
methods: {
foo: function () {
console.log('foo')
},
conflicting: function () {
console.log('from mixin')
}
}
}
var vm = new Vue({
mixins: [mixin],
methods: {
bar: function () {
console.log('bar')
},
conflicting: function () {
console.log('from self')
}
}
})
vm.foo() // => "foo"
vm.bar() // => "bar"
vm.conflicting() // => "from self"
注意:Vue.extend() 也使用同樣的策略進行合并
2、全局混入
當我們存在多個組件中的數(shù)據(jù)或者功能很相近時,我們就可以利用mixins將公共部分提取出來,在 mixin函數(shù)中混入統(tǒng)一的成員。
注意:請謹慎使用全局混入,因為它會影響每個單獨創(chuàng)建的 Vue 實例 (包括第三方組件)。大多數(shù)情況下,只應當應用于自定義選項。推薦將其作為插件發(fā)布,以避免重復應用混入。
<div id="app1">
<p>姓名:<input type="text" v-model="name"></p>
<p>年齡:<input type="text" v-model.number="age"><button @click="age++">++</button></p>
<p>性別:<input type="text" v-model="sex"></p>
<p>稅前工資:<input type="text" v-model="salary">稅后工資:<input type="text" :value="salary2"></p>
<p>汽車信息:{{car}}</p>
<button @click="sayHi">sayHi</button>
<button @click="getSubjects">請求課程數(shù)據(jù)</button>
<div>
{{subjects}}
</div>
</div>
<hr>
<div id="app2">
<p>姓名:<input type="text" v-model="name"></p>
<p>年齡:<input type="text" v-model.number="age"><button @click="age++">++</button></p>
<p>性別:<input type="text" v-model="sex"></p>
<p>稅前工資:<input type="text" v-model="salary">稅后工資:<input type="text" :value="salary2"></p>
<button @click="sayHi">sayHi</button>
</div>
<script>
Vue.config.productionTip = false
// 給所有的vue實例混入統(tǒng)一的成員
Vue.mixin({
data() {
return {
name:'',
age:0,
sex:'男',
salary:1000
}
},
methods: {
sayHi(){
alert(`大家好!我叫${this.name},性別是${this.sex},今年${this.age}歲`)
},
async $get(url,params){
let {data} = await axios.get(url,{params})
return data
},
async $post(url,params){
let {data} = await axios.post(url,params)
return data
}
},
computed:{
salary2(){
return this.salary*0.8
}
},
watch:{
age(val){
if (this.age>100) {
alert('年齡不能超過100')
this.age = 100
}
}
},
mounted() {
console.log('mixin:掛載完成')
},
})
new Vue({
el:'#app1',
data:{
car:{
name:'奔馳',
price:'100W'
},
subjects:[]
},
methods: {
async getSubjects(){
let {data} = await this.$get('http://bingjs.com:81/Subject/GetSubjectsConditionPages',)
this.subjects = data
}
},
mounted() {
console.log('app1:掛載完成')
},
})
new Vue({
el:'#app2',
mounted() {
console.log('app2:掛載完成')
},
})
</script>
效果如圖:
