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

一、v-model

1、v-model的含义

v-model就是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性,也会在更新data绑定属性时候,更新页面上输入控件的值。

2、v-model的基础用法

(1)v-model 指令在表单 <input>、<textarea> 及 <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
(2)v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。
(3)v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。

3、使用示例

当你在input输入框中输入内容的时候,上面p标签里面的数据实时发生变化(实际上是name和age两个数据发生了响应式变化)。相反改变代码中的name和age值,input输入框中值也会实时变化。
其实现原理如下:

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

实现效果如下:


image.png

实现原理:
①首先input输入框通过属性绑定:value="name"&:value="age"得到响应数据name&age.
②定义两个函数,通过e.target得到input框中的value值。
③最后通过input输入框@input事件监听,绑定两个函数(updateName,updateAge),将input框中的value值传给name&age。

vue中的v-model能够实现数据的双向绑定,也是vue的最突出的优势。
v-model实际上是v-bind: 和 v-on:的语法糖。它的实现原理主要包括属性绑定和事件监听两部分。
具体使用如下:

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

实现效果与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,
                }
            },
            // 监听器
            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: {
                // 衣服数量
                yf: 5,
                // 裤子数量
                kz: 5,
                // 鞋子数量
                xz: 5
            }
        })
    </script>

三、插槽

插槽的含义
插槽就是子组件中的提供给父组件使用的一个占位符,用<slot></slot> 表示,父组件可以在这个占位符中填充任何模板代码,如 HTML、组件等,填充的内容会替换子组件的<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>

② 在页面中使用子组件标签,并写入内容

<div id="app">
        <b-box>
            <!-- 如果没有slot插槽,这里的内容将不会显示 -->
            <div>我是匿名插槽</div>
        </b-box>
    </div>

效果如图:


image.png

2、具名插槽

插槽有一个特殊的属性:name,这个属性可以用来定义多个插槽。
在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称。
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>

实现效果如下:
image.png

3、作用域插槽

作用域插槽其实就是可以传递数据的插槽。子组件中的一些数据想在父组件中使用,必须通过规定的方法来传递。
作用域插槽必须是具名插槽,在作用域插槽上可以通过v-bind:绑定属性,绑定的属性,通过指定的作用域变量是接收 。

绑定在元素上的 属性被称为插槽 prop。现在在父级作用域中,我们可以使用带值的 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) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

1、选项合并

当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
比如,数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。

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" }
  }
})

同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

var mixin = {
  created: function () {
    console.log('混入对象的钩子被调用')
  }
}

new Vue({
  mixins: [mixin],
  created: function () {
    console.log('组件钩子被调用')
  }
})

// => "混入对象的钩子被调用"
// => "组件钩子被调用"

值为对象的选项,例如 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、全局混入

当我们存在多个组件中的数据或者功能很相近时,我们就可以利用mixins将公共部分提取出来,在 mixin函数中混入统一的成员。

注意:请谨慎使用全局混入,因为它会影响每个单独创建的 Vue 实例 (包括第三方组件)。大多数情况下,只应当应用于自定义选项。推荐将其作为插件发布,以避免重复应用混入。

 <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">请求课程数据</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实例混入统一的成员
        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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,671评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,442评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,524评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,623评论 1 275
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,642评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,584评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,953评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,621评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,865评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,608评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,698评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,378评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,958评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,940评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,173评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,419评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,425评论 2 342

推荐阅读更多精彩内容