VUE从入门到入坑—07.深入理解v-model / 插槽|具名插槽|作用域插槽 / .sync修饰符 / mixin混入


上篇:VUE从入门到入坑—06. 父子组件通信 / 几种常用的第三方组件库

一、深入理解v-model

1.v-model,可以实现数据双向的绑定,其实就是一个由属性和事件组成的语法糖。

元素类型 属性 事件
input[type=text]、textarea value input
input[checkbox]、input[radio] checked change
select value change

2.将自定义组件,绑定数据的属性改成value,监听事件的名称改成input,就可以使用v-model简写形式。

<div id="app">
    <ul class="list">
        <li>{{yf.label}}--{{yf.count}}</li>
        <li>{{kz.label}}--{{kz.count}}</li>
    </ul>
    <b-counter :label="yf.label" :value="yf.count" @input="yf.count=$event"></b-counter>
    <b-counter :label="kz.label" v-model="kz.count"></b-counter>
</div>
Vue.component('b-counter', {
    template: `
            <div class="counter">
                <div class="label">{{label}}</div>
                <div class="btns">
                    <button @click="myCount--" :disabled="myCount===minCount">-</button>
                    <input readonly class="text" type="text" :value="myCount">
                    <button @click="myCount++" :disabled="myCount===maxCount">+</button>
                </div>
            </div>
            `,
    props: {
        //文本
        label: {
            type: String,
            required: false,
        },
        //数量
        value: {
            type: Number,
            required: true
        },
        //最大值
        maxCount: {
            type: Number,
            default: 999
        },
        //最小值
        minCount: {
            type: Number,
            default: 1
        }
    },
    //定义数据
    data() {
        return {
            myCount: this.value
        }
    },
    //监听器
    watch: {
        myCount(val) {
            // 触发一个自定义事件,事件名称是input
            this.$emit('input', val)
        }
    }
})

let vm = new Vue({
    el: '#app',
    data: {
        // 衣服
        yf: {
            label: '衣服',
            count: 5
        },
        // 裤子
        kz: {
            label: '裤子',
            count: 5
        }
    }
})

效果:

二、插槽

1.组件中的插槽slot,用于在组件的内部定义插槽,组件标签之间的所有html内容,会在插槽所在位置呈现。

    <div id="app">
        <b-tab :list="list" :active="activeIndex">
            <h2>全国著名小吃</h2>
        </b-tab>
    </div>
        Vue.component('b-tab', {
            template: `
            <div class="tab">
                <slot></slot>
                <ul class="titles">
                    <li @click="activeIndex=index" :class="{active:activeIndex===index}" v-for="(item,index) in list" :key="index">{{item.title}}</li>
                </ul>
                <ul class="contents">
                    <li v-show="activeIndex===index" v-for="(item,index) in list" :key="index">{{item.content}}</li>
                </ul>
            </div>
            `,
            props: ['list', 'active'],
            data() {
                return {
                    activeIndex: this.active
                }
            }
        })
        new Vue({
            el: '#app',
            data: {
                // 高亮索引
                activeIndex: 0,
                list: [
                    {
                        title: '北京',
                        content: '北京的糖葫芦真好吃'
                    },
                    {
                        title: '南京',
                        content: '南京的盐水鸭真好吃'
                    },
                    {
                        title: '武汉',
                        content: '武汉的热干面真好吃'
                    },
                    {
                        title: '长沙',
                        content: '长沙的臭豆腐真好吃'
                    }
                ]
            }
        })

效果:

2.具名插槽,在组件内部通过slot标签定义插槽,再通过name属性给插槽定义名字,这样的插槽称之为具名插槽。插槽的默认名称是:default。如果有多个插槽,允许其中一个插槽不定义名称。在template标签中采用v-slot:插槽名称的方式,指定内容在哪一个具体的插槽中呈现。#是v-slot:的简写。

<div id="app">
    <b-box>
        <!-- 在template组件中采用v-slot:插槽名称的方式,指定使用哪个插槽 -->
        <template v-slot:house>
            <div>有5套房子</div>
        </template>
        <!-- #是v-slot:的简写 -->
        <template #car>
            <div>有8辆汽车</div>
        </template>
        <template v-slot:money>
            <div>有3千万存款</div>
        </template>
    </b-box>
</div>
Vue.component('b-box', {
    template: `
        <div class="box">
            <div class="item">
                <h2>房产信息</h2>
                <slot name="house"></slot>
            </div>
            <div class="item">
                <h2>车辆信息</h2>
                <slot name="car"></slot>
            </div>
            <div class="item">
                <h2>存款信息</h2>
                <slot name="money"></slot>
            </div>
        </div>
    `
})
new Vue({
    el: '#app',
})

3.作用域插槽,必须是具名插槽;在作用域插槽上,可以通过v-bind:绑定属性,绑定的属性通过指定的作用域变量(通常会定义scope变量,变量名随意取)去接收。

<div id="app">
    <b-box>
        <template v-slot: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>
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:5999
                },
                {
                    id:1002,
                    name:'华为手机',
                    price:6999
                },
                {
                    id:1003,
                    name:'小米手机',
                    price:7999
                },
                {
                    id:1004,
                    name:'三星手机',
                    price:8999
                }
            ]
        }
    }
})
new Vue({
    el:'#app',
    methods: {
        priceDown(list,index){
            list[index].price-=1000
        },
        priceUp(list,index){
            list[index].price+=1000
        }
    },
})

三、.sync修饰符

1.绑定属性采用:xx.sync修饰符,可以省略update:xx对应的事件绑定。
2.必要条件①:属性绑定必须是xx.sync;必要条件②:自定义事件必须是update:xx。
3.如果触发的事件名称是update:属性名,那么就可以使用.sync修饰符简化调用的过程。
4.总结:如果组件只回传一份数据,用v-model。如果组件回传多份数据,用.sync修饰符。

<div id="app">
    <div>衣服:{{yf}},裤子:{{kz}},鞋子:{{xz}}</div>
    <!-- 属性绑定必须是:xx.sync -->
    <!-- :yf.sync="yf" @update:yf="yf=$event"可以简写成 :yf.sync="yf" -->
    <b-counter :yf.sync="yf" @update:yf="yf=$event" :kz.sync="kz" :xz.sync="xz"></b-counter>
</div>
Vue.component('b-counter', {
    template: `
        <div>
            <div class="counter">
                <div class="label">衣服:</div>
                <div class="btns">
                    <button @click="yfCount--">-</button>
                    <input readonly class="text" type="text" :value="yfCount">
                    <button @click="yfCount++">+</button>
                </div>
            </div>
            <div class="counter">
                <div class="label">裤子:</div>
                <div class="btns">
                    <button @click="kzCount--">-</button>
                    <input readonly class="text" type="text" :value="kzCount">
                    <button @click="kzCount++">+</button>
                </div>
            </div>
            <div class="counter">
                <div class="label">鞋子:</div>
                <div class="btns">
                    <button @click="xzCount--">-</button>
                    <input readonly class="text" type="text" :value="xzCount">
                    <button @click="xzCount++">+</button>
                </div>
            </div>
        </div>
    `,
    props: ['yf', 'kz', 'xz'],
    data() {
        return {
            yfCount: this.yf,
            kzCount: this.kz,
            xzCount: this.xz
        }
    },
    watch:{
        yfCount(val){
            // 自定义事件必须是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
    }
})

四、mixin混入

1.混入 (mixin),提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。mixin()方法的参数是配置对象,Vue实例可以配置的东西,它都可以配置。比如:数据,方法,生命周期钩子函数,计算属性,侦听器,过滤器,等等。

2.全局混入的内容,之后创建的所有Vue实例包括组件实例都将拥有。在创建Vue实例时,会将mixin里面的成员跟Vue实例自身的成员进行合并,如果冲突了,最终采用Vue实例身上的成员。

特别注意:生命周期钩子不是合并,是叠加执行,是先执行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>
    <button @click='sayHi'>sayHi</button>
    <div>汽车信息:{{car}}</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>飞机信息:{{plane}}</div>
</div>
//全局混入,给所有的Vue实例混入统一的成员 -- 注意:必须要先执行
Vue.mixin({
    data() {
        return {
            name: '',
            age: 0,
            sex: '男',
            salary: 1000
        }
    },
    computed: {
        salary2() {
            return this.salary * 0.88
        }
    },
    methods: {
        sayHi() {
            alert(`大家好!我叫${this.name},性别是${this.sex},今年${this.age}岁`)
        }
    },
    watch: {
        age(val) {
            if (val > 100) {
                alert('年龄不能超过100岁')
                this.age = 100
            }
        }
    },
    mounted() {
        console.log('mixin:组件挂载完成');
    },
})
// 创建第一个Vue的实例--操作的容器是#app1
new Vue({
    el: '#app1',
    data() {
        return {
            car: {
                name: '奔驰',
                price: '100W'
            }
        }
    },
    // 注意:先执行混入的生命周期,再执行自己的生命周期
    mounted() {
        console.log('app1:组件挂载完成');
    },
})
// 创建第二个Vue的实例--操作的容器是#app2
new Vue({
    el: '#app2',
    data() {
        return {
            plane: {
                name: '波音747',
                price: '100Y'
            }
        }
    },
})

效果:

五、混入ajax的基本操作

1.请求ajax相关的操作的时候也可以通过mixin混入给Vue。

<div id="app1">
    <button @click="getSubjects">请求课程数据</button>
    <div>
        {{subjects}}
    </div>
</div>
Vue.mixin({
    methods: {
        //get请求
        async $get(url, params) {
            let { data } = await axios.get(url, { params })
            return data
        },
        //post请求
        async $post(url, params) {
            let { data } = await axios.post(url, params)
            return data
        }
    }
})

new Vue({
    el: '#app1',
    data() {
        return {
            //课程数组
            subjects: []
        }
    },
    methods: {
        async getSubjects() {
            let { data } = await this.$get('http://www.bingjs.com:81/Subject/GetSubjectsConditionPages')
            this.subjects = data
        }
    }
})

Over!

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

推荐阅读更多精彩内容