vue-v-model的深入了解&sync修飾符&插槽&混入

一、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)效果如下:


image.png

實現(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:相同:


image.png

二、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>

效果如圖:


image.png

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)效果如下:
image.png

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>

效果如圖:


image.png
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容