Vue3——组件

1、注册组件

在使用组件之前需要将组件注册到应用中。Vue.js提供了两种注册方式,分别是全局注册和局部注册,下面分别进行介绍。

1.1、注册全局组件

全局注册的组件也叫全局组件。注册一个全局组件的语法格式如下:

 vm.component(tagName, options)

两个参数的说明如下:

  • tagName:表示定义的组件名称。对于组件的命名,建议遵循W3C规范中的自定义组件命名方式,即字母全部小写并包含一个连字符“-”​。
  • options:该参数表示组件的选项对象。因为组件是可复用的Vue实例,所以它们与一个Vue实例一样接收相同的选项,如data、computed、watch、methods以及生命周期钩子等。

在注册组件后,组件以自定义元素的形式进行使用。使用组件的方式如下:

 <tagName></tagName>

例如,注册一个简单的全局组件。代码如下:

<div id="app">
    <demo></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
            }
        },
    });
    vm.component('demo', {
        template: '<h2>一寸光阴一寸金</h2>'
    });
    vm.mount('#app');
</script>

在这里插入图片描述

template选项用于定义组件的模板(组件的内容)​。在使用组件时,组件所在位置将被替换为template选项的内容。

组件的模板只能有一个根元素。如果模板内容有多个元素,可以将模板的内容包含在一个父元素内。示例代码如下:

<div id="app">
    <demo></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
            }
        },
    });
    vm.component('demo', {
        template: `<div>
            <p>春晓</p>
            <div>春眠不觉晓,</div>
            <div>处处闻啼鸟。</div>
            <div>夜来风雨声,</div>
            <div>花落知多少。</div>` 
    });
    vm.mount('#app');
</script>

在这里插入图片描述

在组件的选项对象中可以使用data选项定义数据。示例代码如下:

<div id="app">
    <count-button></count-button>
    <count-button></count-button>
    <count-button></count-button>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
            }
        },
    });
    vm.component('count-button', {
        data() {
            return {
                count: 0
            }
        },
        template: `<button @click="count++">{{count}}`
    });
    vm.mount('#app');
</script>

上述代码中定义了3个相同的按钮组件。当单击某个按钮时,每个组件会各自独立维护其count属性,因此单击一个按钮时其他组件不会受到影响。运行结果如图所示。

在这里插入图片描述

1.2、注册局部组件

通过Vue实例中的components选项可以注册一个局部组件。对于components对象中的每个属性来说,其属性名就是定义组件的名称,其属性值就是这个组件的选项对象。例如,注册一个简单的局部组件。示例代码如下:

<div id="app">
    <demo></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
            }
        },
        components: {
            'demo': {
                template: `<h2>寸金难买寸光阴</h2>`
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

局部注册的组件只能在其父组件中使用,而无法在其他组件中使用。例如,有两个局部组件componentA和componentB,如果希望componentA在componentB中可用,则需要将componentA定义在componentB的components选项中。示例代码如下:

<div id="app">
    <parent></parent>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    let Child = {
        template: `<h2>坚持就是成功的终点</h2>`
    }
    let Parent = {
        template: `<div>
            <h2>相信是成功的起点</h2>
            <child></child>
            </div>`,
        components: {
            'child': Child
        }
    }
    const vm = Vue.createApp({
        components: {
            'parent': Parent
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

2、向子组件传递数据

2.1、Prop基本用法

由于组件实例的作用域是孤立的,因此子组件的模板无法直接引用父组件的数据。如果想要在父子组件之间传递数据,就需要定义Prop。Prop是父组件用来传递数据的一个自定义属性,这样的属性需要定义在组件选项对象的props选项中。通过props选项中定义的属性可以将父组件的数据传递给子组件,而子组件需要显式地用props选项来声明Prop。

2.1.1、传递静态数据

使用Prop可以传递一个常量值,它是一个静态数据。示例代码如下:

<div id="app">
    <demo text="自我控制是最强者的本能"></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        components: {
            'demo': {
                props: ['text'],
                template: `<h3>{{text}}</h3>`
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

一个组件默认可以拥有任意数量的Prop,任何值都可以传递给Prop。

2.1.2、Prop的书写规则

由于HTML中的属性是不区分大小写的,因此浏览器会把所有大写字符解释为小写字符。如果在调用组件时使用了小驼峰式命名的属性,那么在props中的命名需要全部小写。示例代码如下:

<div id="app">
    <demo myText="要向大目标走去,就得从小目标开始。"></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        components: {
            'demo': {
                props: ['mytext'],
                template: `<h3>{{mytext}}</h3>`
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

如果props中的命名采用小驼峰的方式,那么在调用组件时需要使用其等价的短横线分隔的命名方式来命名属性。将上面的示例代码修改如下:

<div id="app">
    <demo my-text="要向大目标走去,就得从小目标开始。"></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        components: {
            'demo': {
                props: ['myText'],
                template: `<h3>{{myText}}</h3>`
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

2.1.3、传递动态数据

除了上述示例中传递静态数据的方式,也可以通过v-bind的方式将父组件中的data数据传递给子组件。每当父组件的数据发生变化时,子组件也会随之变化,通过这种方式传递的数据叫动态Prop。示例代码如下:

<div id="app">
    <demo :name="name" :position="position" :year="year"></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                name: '张三',
                position: '前端工程师',
                year: 10
            }
        },
    });
    vm.component('demo', {
        props: ['name', 'position', 'year'],
        template: `<div>
            <p>姓名:{{name}}</p>
            <p>职位:{{position}}</p>
            <p>工龄:{{year}}年</p>
            </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

示例:输出商品信息。

应用动态Prop传递数据,输出商品的图片、名称和简介等信息,实现步骤如下。

  1. 定义<div>元素,并设置其id属性值为app,在该元素中调用组件my-goods,同时传递三个动态Prop,将商品的图片、名称和简介作为传递的值。代码如下:
 <div id="app">
      <my-goods :img="imgUrl" :name="name" :intro="intro"></my-goods>
 </div>
  1. 编写CSS代码,为页面元素设置样式。具体代码如下:
<style>
    body{
        font-family: 微软雅黑;
    }
    img{
        width: 300px;
    }
    .goods_name{
        padding-left: 10px;
        font-size: 18px;
        color: #333333;
        margin-top: 8px;
    }
    .goods_intro{
        padding-left: 10px;
        font-size: 14px;
        margin-top: 5px;
    }
</style>
  1. 创建根组件实例,在实例的data选项中定义商品的图片、名称和简介信息,在实例下方注册全局组件my-goods,在props选项中定义传递的Prop,在组件的模板中输出商品的图片、名称和简介。代码如下:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                imgUrl: 'images/1.jpg',
                name: '儿童数码相机',
                intro: '录像功能,好玩又有趣,搞清拍照,捕捉精彩瞬间'
            }
        },
    });
    vm.component('my-goods', {
        props: ['img', 'name', 'intro'],
        template: `<div>
            <img :src="img">
            <div class="goods_name">商品名称:{{name}}</div>
            <div class="goods_intro">商品简介:{{intro}}</div>
            </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

使用Prop传递的数据除了可以是数值和字符串类型,还可以是数组或对象类型。例如,使用Prop传递数组类型的数据,代码如下:

<div id="app">
    <my-demo :types="types"></my-demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                types: ['HTML', 'CSS', 'JavaScript', 'Vue.js']
            }
        }
    });
    vm.component('my-demo', {
        props: ['types'],
        template: `<ol>
                <li v-for="type in types">{{type}}</li>
            </ol>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

如果Prop传递的是一个对象或数组,那么它是按引用传递的。在子组件内修改这个对象或数组本身将会影响父组件的状态。

在传递对象类型的数据时,如果想要将一个对象的所有属性都作为Prop传入,可以使用不带参数的v-bind。示例代码如下:

<div id="app">
    <my-demo v-bind="info"></my-demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                info: {
                    name: '李四',
                    position: '系统管理员',
                    year: 6
                }
            }
        }
    });
    vm.component('my-demo', {
        props: ['name', 'position', 'year'],
        template: `<div>
            <p>姓名:{{name}}</p>
            <p>职位:{{position}}</p>
            <p>工龄:{{year}}年</p>
            </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

2.2、 数据验证

组件可以为Prop指定验证要求。当开发一个可以被他人使用的组件时,验证可以让使用者更加准确地使用组件。使用验证的时候,Prop接收的参数是一个对象,而不是一个字符串数组。例如,props :{n : Number},表示验证参数n须为Number类型,如果调用该组件时传入的n为Number以外的类型,则会抛出异常。Vue.js提供的Prop验证方式有多种,下面分别进行介绍。

  1. 基础类型检测。允许参数为指定的一种类型。

示例代码如下:

 props : {
    username : String
}

上述代码表示参数username允许的类型为字符串类型。可以接收的参数类型为:

  • String
  • Number
  • Boolean
  • Array
  • Object
  • Date
  • Function
  • Symbol。
  • 也可以接收null和undefined,表示任意类型均可。
  1. 多种类型。允许参数为多种类型之一。

示例代码如下:

props : {
     tel : [Number, String]
}

上述代码表示参数tel可以是数值类型或字符串类型。

  1. 参数必需。参数必须有值且为指定的类型。

示例代码如下:

props : {
     address : {
          type : String,
          required : true
     }
}

上述代码表示参数address必须有值且为字符串类型。

  1. 参数默认。参数具有默认值。

示例代码如下:

props : {
     sex : {
          type : String,
          default : '男'
     }
}

上述代码表示参数sex为字符串类型,默认值为“男”​。需要注意的是,如果参数类型为数组或对象,则其默认值需要通过函数返回值的形式获取。示例代码如下:

props: {
	interest: {
		type: Array,
		default: function() {
			return ['看书', '绘画', '写作']
		}
	}
}
  1. 自定义验证函数。根据验证函数验证参数的值是否符合要求。

示例代码如下:

props: {
	age: {
		validator: function(value){
			return value >= 18;
		}
	}
}

上述代码表示参数age的值必须大于或等于18。

对组件中传递的数据进行Prop验证的示例代码如下:

<div id="app">
    <my-demo :name="'王五'" :age=25></my-demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
       
    });
    vm.component('my-demo', {
        props: {
            //是否有值且为字符串
            name: {
                type: String,
                required: true
            },
            //是否为字符串且默认值为男
            sex: {
                type: String,
                default: '男'
            },
            //是否为数值类型且值是否大于或等于18
            age: {
                type: Number,
                validator: function(value) {
                    return value >= 18
                }
            },
            //是否为数组且有默认值
            interest: {
                type: Array,
                default: function() {
                    return ['看书', '绘画', '写作']
                }
            },
            //是否为对象类型且有默认值
            contact: {
                type: Object,
                default: function() {
                    return {
                        address: '吉林省长春市',
                        tel: '13312345678'
                    }
                }
            }
        },
        template: `<div>
            <p>姓名:{{name}}</p>
            <p>性别:{{sex}}</p>
            <p>年龄:{{age}}年</p>
            <p>兴趣爱好:{{interest.join('、')}}</p>
            <p>联系地址:{{contact.address}}</p>
            <p>联系电话:{{contact.tel}}</p>
            </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

在开发环境中,如果Prop验证失败,则Vue将产生一个控制台的警告。

3、监听子组件事件

3.1、监听自定义事件

父组件通过使用Prop为子组件传递数据,但如果子组件要把数据传递回去,就需要使用自定义事件来实现。父组件可以通过v-on指令监听子组件实例的自定义事件,而子组件可以通过调用内建的$emit()方法并传入事件名称来触发自定义事件。

$emit()方法的语法格式如下:

 vm.$emit( eventName, […args] )
  • eventName:传入事件的名称。
  • […args]​:触发事件传递的参数。该参数是可选的。

示例:通过单击按钮设置粗体文本。

在页面中定义一个按钮和一行文本,通过单击按钮实现设置粗体文本的效果。代码如下:

<div id="app">
    <div :style="{fontWeight: weight}">
        <my-text :text="text" @setweight="weight='bold'"></my-text>
    </div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                text: '千里之行,始于足下',
                weight: ''
            }
        }
    });
    vm.component('my-text', {
        props: ['text'],
        template: `<div>
            <button @click="action">设置粗体文本</button>
            <p>{{text}}</p>
            </div>`,
        methods: {
            action: function() {
                this.$emit('setweight');
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

在这里插入图片描述

有些时候需要在自定义事件中传递一个特定的值,这时可以使用 e m i t ( ) 方法的第二个参数来实现。然后在父组件监听这个事件的时候,可以通过 ‘ emit()方法的第二个参数来实现。然后在父组件监听这个事件的时候,可以通过` emit()方法的第二个参数来实现。然后在父组件监听这个事件的时候,可以通过event`访问到传递的这个值。

例如,将上面的代码进行修改,实现单击“设置粗体文本”按钮时,将文本设置为更粗的字体。修改后的代码如下:

<div id="app">
    <div :style="{fontWeight: weight}">
        <my-text :text="text" @setweight="weight=$event"></my-text>
    </div>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                text: '千里之行,始于足下',
                weight: ''
            }
        }
    });
    vm.component('my-text', {
        props: ['text'],
        template: `<div>
            <button @click="action('bolder')">设置粗体文本</button>
            <p>{{text}}</p>
            </div>`,
        methods: {
            action: function(par) {
                this.$emit('setweight', par);
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

在父组件监听自定义事件的时候,如果事件处理程序是一个方法,那么通过$emit()方法传递的参数将会作为第一个参数传入这个方法。下面通过一个实例来说明。

示例:输出商品信息。

定义一个“显示商品信息”按钮,单击该按钮在下方显示定义的商品信息,实现步骤如下。

  1. 定义<div>元素,并设置其id属性值为app,在该元素中调用组件my-goods,通过v-on指令的简写形式监听子组件实例的自定义事件getdata,当触发事件时调用根实例中的show()方法。代码如下:
<div id="app">
    <my-goods @getdata="show"></my-goods>
    <div v-show="flag">
        <p>商品名称:{{name}}</p>
        <p>商品价格:{{price}}元</p>
        <p>商品数量:{{number}}</p>
    </div>
</div>
  1. 创建根实例,在实例中定义数据和方法,在实例下方注册全局组件my-goods,在选项对象中定义传递的数据对象,在组件的模板中定义一个“显示商品信息”按钮,在methods选项中定义active()方法,当单击按钮时会调用该方法,在方法中通过$emit()方法触发自定义事件getdata,同时将定义的数据对象info作为参数。触发自定义事件后,通过调用根实例中的show()方法输出定义的商品信息。代码如下:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                flag: false,
                name:'',
                price: 0,
                number: 0
            }
        },
        methods: {
            show: function(info) {
                this.flag = true;
                this.name = info.name;
                this.price = info.price;
                this.number = info.number;
            }
        }
    });
    vm.component('my-goods', {
        data() {
            return {
                info: {
                    name: '笔记本电脑',
                    price: 3699,
                    number: 10
                }
            }
        },
        template: `<button @click="active">显示商品信息</button>`,
        methods: {
            active: function(value) {
                this.$emit('getdata', this.info);
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

3.2、监听原生事件

在Vue 3.0之前,如果想让某个组件监听一个原生事件,可以使用v-on指令的.native修饰符。而在Vue 3.0中删除了v-on指令的.native修饰符,Vue 3.0会将子组件中自定义事件以外的所有事件监听器作为原生事件添加到子组件的根元素上。例如,在组件的根元素上监听mouseover和mouseout事件,当鼠标移入文本时将文本设置为斜体,当鼠标移出文本时使文本恢复为原来的样式,代码如下:

<div id="app">
    <demo :style="show" @mouseover="setStyle('italic')" @mouseout="setStyle()"></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                size: '20px',
                sty: '',
                cursor: 'pointer'
            }
        },
        methods: {
            setStyle: function(value) {
                this.sty = value;
            }
        },
        computed: {
            show: function() {
                return {
                    fontSize: this.size,
                    fontStyle: this.sty,
                    cursor: this.cursor
                }
            }
        }
    });
    vm.component('demo', {
        template: `<span>成功永远属于马上行动的人</span>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

4、插槽的使用

在实际开发中,子组件往往只提供基本的交互功能,而内容是由父组件来提供的。为此,Vue.js提供了一种混合父组件内容和子组件模板的方式,这种方式称为内容分发。

4.1、基础用法

Vue.js参照当前Web Components规范草案实现了一套内容分发的API,使用元素作为原始内容的插槽。下面通过一个示例来说明插槽的基础用法。

<div id="app">
    <demo-slot>
        {{msg}}
    </demo-slot>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                msg: '路遥知马力,日久见人心'
            }
        }
    });
    vm.component('demo-slot', {
        template: `<div class="content">
                        <slot></slot>
                  </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

上述代码的渲染结果为:

<div class="content">
  路遥知马力,日久见人心。
</div>

由渲染结果可以看出,父组件中的内容{{msg}}会代替子组件中的标签,这样就可以在不同地方使用子组件的结构并且填充不同的父组件内容,从而提高组件的复用性。

如果组件中没有包含一个<slot>元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。

4.2、编译作用域

上述示例代码在父组件中调用<demo-slot>组件,并绑定了父组件中的数据msg。其中的msg只能在父组件的作用域下进行解析,而不能在<demo-slot>组件的作用域下进行解析。也就是说,父组件模板里的所有内容都是在父组件作用域中编译的;子组件模板里的所有内容都是在子组件作用域中编译的。例如,下面这个父组件模板的例子是不会输出任何结果的。

<div id="app">
    <demo-slot>
        {{msg}}
    </demo-slot>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('demo-slot', {
        data() {
            return {
                msg: '路遥知马力,日久见人心'
            }
        },
        template: `<div class="content">
                        <slot></slot>
                  </div>`
    });
    vm.mount('#app');
</script>

上述代码的渲染结果为:

 <div class="content">
</div>

4.3、默认内容

有些时候需要为一个插槽设置默认内容,该内容只会在没有提供内容的时候被渲染。示例代码如下:

<div id="app">
    <my-checkbox></my-checkbox>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('my-checkbox', {
        template: `<div>
                    <input type="checkbox">
                    <slot>选中表示同意注册条款</slot>
                  </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

如果提供了内容,则该提供的内容将会替代默认内容从而被渲染。示例代码如下:

<div id="app">
    <my-checkbox>{{text}}</my-checkbox>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                text: '提供的数据'
            }
        }
    });
    vm.component('my-checkbox', {
        template: `<div>
                    <input type="checkbox">
                    <slot>选中表示同意注册条款</slot>
                  </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

4.4、命名插槽

如果要在组件模板中使用多个插槽,就需要用到元素的name属性。通过这个属性可以为插槽命名。在向命名的插槽提供内容时,可以在一个<template>元素上使用v-slot指令,将插槽的名称作为v-slot指令的参数。这样,<template>元素中的所有内容都将被传入相应的插槽。示例代码如下:

<div id="app">
    <demo-slot>
        <!-- v-slot指令的参数需要与子组件中slot元素的name值匹配 -->
        <template v-slot:name>
            <div>商品名称:{{name}}</div>
        </template>
        <template v-slot:connect>
            <div>连接方式:{{connect}}</div>
        </template>
        <template v-slot:color>
            <div>商品颜色:{{color}}</div>
        </template>
    </demo-slot>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                name: '无线鼠标',
                connect: '蓝牙',
                color: '黑色'
            }
        }
    });
    vm.component('demo-slot', {
        template: `<div>
            <div class="name">
                <slot name="name"></slot>
            </div>
            <div class="connect">
                <slot name="connect"></slot>
            </div>
            <div class="color">
                <slot name="color"></slot>
            </div>
        </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

一个未设置name属性的插槽称为默认插槽,它有一个隐含的name属性值default。如果有些内容没有被包含在带有v-slot的<template>中,则这部分内容都会被视为默认插槽的内容。下面通过一个实例来说明默认插槽的用法。

示例:输出歌曲信息。

在页面中输出歌曲《我心永恒》的基本信息,包括歌曲名称、歌曲原唱、音乐风格、歌曲语言和发行时间,并将歌曲名称作为默认插槽的内容,实现步骤如下。

  1. 定义<div>元素,并设置其id属性值为app,在该元素中调用组件demo-slot,在4个<template>元素上分别使用v-slot指令,将插槽的名称作为该指令的参数,并将歌曲名称作为默认插槽的内容。代码如下:
<div id="app">
    <demo-slot>
        歌曲名称:{{name}}<!--默认插槽的内容-->
        <template v-slot:singer>
            歌曲原唱:{{singer}}
        </template>
        <template v-slot:style>
            音乐风格:{{style}}
        </template>
        <template v-slot:language>
            歌曲语言:{{language}}
        </template>
        <template v-slot:time>
            发行事件:{{time}}
        </template>
    </demo-slot>
</div>
  1. 编写CSS代码,为页面元素设置样式。具体代码如下:
<style>
    body{
        font-family: 微软雅黑;
    }
    .name, .singer, .style, .language, time {
        margin-top: 8px;
        font-size: 16px;
    }
</style>
  1. 创建根实例,在实例中定义数据,在data选项中定义歌曲名称、歌曲原唱、音乐风格、歌曲语言和发行时间。在实例下方注册全局组件demo-slot,在组件的模板中定义一个默认插槽和4个命名插槽。代码如下:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                name: '我心永恒',
                singer: '席琳迪翁',
                style: '民谣/流行',
                language: '英语',
                time: '1997-12-8'
            }
        }
    });
    vm.component('demo-slot', {
        template: `<div>
            <div class="name">
                <slot></slot>
            </div>
            <div class="singer">
                <slot name="singer"></slot>
            </div>
            <div class="style">
                <slot name="style"></slot>
            </div>
            <div class="language">
                <slot name="language"></slot>
            </div>
            <div class="time">
                <slot name="time"></slot>
            </div>
        </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

为了使代码看起来更明确,可以将默认插槽的内容使用一个<template>元素包含起来。例如,将例11.4中默认插槽的内容包含在一个<template>元素中,代码如下:

<template v-slot:default>
     歌曲名称:{{name}}
</template>

4.5、作用域插槽

有些时候需要让插槽内容能够访问子组件中才有的数据。为了让子组件中的数据在父级的插槽内容中可用,可以将子组件中的数据作为一个元素的属性并对其进行绑定。绑定在元素上的属性被称为插槽Prop。然后在父级作用域中,可以为v-slot设置包含所有插槽Prop的对象的名称。示例代码如下:

<div id="app">
    <demo-slot>
        <template v-slot:default="slotProps">
            姓名:{{slotProps.pname}}<br>
            绰号:{{slotProps.nickname}}
        </template>
    </demo-slot>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
    });
    vm.component('demo-slot', {
        data() {
            return {
                pname: '吴用',
                nickname: '智多星'
            }
        },
        template: `<span>
            <slot :pname="pname" :nickname="nickname"></slot>
            </span>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

上述代码中,将子组件中的数据pname和nickname作为<slot>元素绑定的属性,然后在父级作用域中,为v-slot设置的包含所有插槽Prop的对象的名称为slotProps,再通过{{slotProps.pname}}{{slotProps.nickname}}即可访问子组件中的数据pname和nickname。

如果只有一个默认插槽,那么组件的标签可以当作插槽的模板来使用。这样就可以把v-slot直接用在组件上。例如,上述示例中使用组件的代码可以简写为:

<demo-slot v-slot:default="slotProps">
     姓名:{{slotProps.pname}}<br>
     绰号:{{slotProps.nickname}}
</demo>

示例:输出电影信息列表。

在页面中输出五部电影的信息列表,包括编号、电影名称、主演和电影简介,实现步骤如下。

  1. 定义<div>元素,并设置其id属性值为app,在该元素中调用组件movie-info,同时传递Prop。在<template>元素中为v-slot设置的包含所有插槽Prop的对象的名称为slotProps。代码如下:
<div id="app">
    <movie-info :items="movies" odd-bgcolor="#AACCFF" even-bgcolor="#EEDDEE">
        <template v-slot:default="slotProps">
            <span>{{movies[slotProps.index].id}}</span>
            <span>{{movies[slotProps.index].name}}</span>
            <span>{{movies[slotProps.index].actor}}</span>
            <span>{{movies[slotProps.index].intro}}</span>
        </template>
    </movie-info>
</div>
  1. 编写CSS代码,为页面元素设置样式。具体代码如下:
<style>
    body{
        font-family: 微软雅黑;
    }
    div span{
        display: inline-block;
        width: 160px;
        text-align:center;
    } 
    .title, .content{
        width: 570px;
        line-height: 2.3;
    }
    .titel span{
        font-size: 18px;
    }
    div span:first-child{
        width: 50px;
    }
    div span:last-child{
        width: 200px;
    }
</style>
  1. 创建根组件实例,在实例中定义数据,将每部电影的编号、电影名称、主演和电影简介作为一个对象定义在一个数组中。在实例下方注册全局组件movie-info,在props选项中定义传递的Prop,在组件的模板中对传递的电影信息列表进行渲染,并为奇数行和偶数行应用不同的背景颜色,将渲染列表时的index索引作为元素的属性并对其进行绑定。代码如下:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                movies: [
                    {id:1, name: '泰坦尼克号', actor: '莱昂纳多迪卡普里奥', intro: '完美而残缺的爱情故事'},
                    {id:2, name: '金蝉脱壳', actor: '史泰龙', intro: '量大动作巨星强强联手'},
                    {id:3, name: '爱乐之城', actor: '瑞恩高斯林', intro: '爱情与梦想的交织'},
                    {id:4, name: '阿甘正传', actor: '汤姆汉克斯', intro: '励志而传奇的一生'},
                    {id:5, name: '阿拉丁', actor: '威尔史密斯', intro: '超过原版动画的真人电影'}
                ]
            }
        }
    });
    vm.component('movie-info', {
        props: {
            items: Array,
            oddBgcolor: String,
            evenBgcolor: String
        },
        template: `<div>
            <div class="title">
                <span>编号</span>
                <span>电影名称</span>
                <span>主演</span>
                <span>简介</span>
            </div>
            <div class="content" v-for="(item, index) in items" :style="index % 2 === 0 ? 'background:' + 
                oddBgcolor :'background:' + evenBgcolor">
                <slot :index="index"></slot>
            </div>
            </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

5、混入

5.1、基础用法

混入是一种为组件提供可复用功能的非常灵活的方式。混入对象可以包含任意的组件选项。当组件使用混入对象时,混入对象中的所有选项将被混入该组件本身的选项中。示例代码如下:

<div id="app">
    <demo></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    //定义一个混入对象
    let mixin = {
        data() {
            return {
                message: '精诚所至,金石为开'
            }
        }
    };
    const vm = Vue.createApp({
    });
    //定义一个使用混入对象的组件
    vm.component('demo', {
        mixins: [mixin],
        template: `<div>
                <h3>成语</h3>
                <p>{{message}}</p>
            </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

5.2、选项合并

当组件和混入对象包含同名选项时,这些选项将以适当的方式合并。例如,数据对象在内部会进行递归合并,在和组件的数据发生冲突时组件数据优先。示例代码如下:

<div id="app">
    <demo></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    //定义一个混入对象
    let mixin = {
        data: function() {
            return {
                poet: '李白',
                works: '静夜思'
            }
        }
    };
    const vm = Vue.createApp({
    });
    //定义一个使用混入对象的组件
    vm.component('demo', {
        mixins: [mixin],
        data: function() {
            return {
                works: '早发白帝城',
                alias: '诗仙'
            }
        },
        template: `<div>
                <div>诗人:{{poet}}</div>
                <div>别名:{{alias}}</div>
                <div>主要作品:{{works}}</div>
            </div>`
    });
    vm.mount('#app');
</script>

在这里插入图片描述

同名钩子函数将混合为一个数组,因此都会被调用。另外,混入对象的钩子会先进行调用,组件自身的钩子后进行调用。示例代码如下:

<div id="app">
    <demo></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    //定义一个混入对象
    let mixin = {
        created: function() {
            this.showVerse();
        },
        methods: {
            showVerse: function() {
                document.write('窗含西岭千秋雪,<br>');
            }
        }
    };
    const vm = Vue.createApp({
    });
    //定义一个使用混入对象的组件
    vm.component('demo', {
        mixins: [mixin],
        template: `<div>
                <p>绝句</p>
                <div>两个黄鹂鸣翠柳,</div>
                <div>一行白鹭上青天。</div>
            </div>`,
        created: function() {
            document.write('门泊东吴万里船。');
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

值为对象的选项,如methods、computed和components等,在合并时将被合并为同一个对象。如果两个对象的键名冲突,则取组件对象的键值对。示例代码如下:

<div id="app">
    <demo></demo>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    //定义一个混入对象
    let mixin = {
        methods: {
            showName: function() {
                document.write('商品名称:品牌手机<br>');
            },
            showPrice: function() {
                document.write('商品价格:2799元<br>');
            }
        }
    };
    const vm = Vue.createApp({
    });
    //定义一个使用混入对象的组件
    vm.component('demo', {
        mixins: [mixin],
        methods: {
            showPrice: function() {
                document.write('商品价格:2599元<br>');
            },
            showColor: function() {
                document.write('商品颜色:黑色<br>');
            }
        },
        created: function() {
            this.showName();
            this.showPrice();
            this.showColor();
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

6、动态组件

6.1、动态组件的用法

Vue.js提供了对动态组件的支持。在使用动态组件时,多个组件使用同一挂载点,根据条件在不同组件之间进行动态切换。动态组件通过使用Vue.js中的<component>元素,动态绑定到该元素的is属性,根据is属性的值来判断使用哪个组件。

动态组件经常应用在路由控制或选项卡切换中。下面通过一个切换选项卡的实例来说明动态组件的基础用法。

示例:实现编程语言图书和办公软件图书之间的切换。

有两种类型的科技类图书信息,一种是编程语言类,另一种是办公软件类。应用动态组件实现这两类图书之间的切换。实现步骤如下。

  1. 定义<div>元素,并设置其id属性值为app,在该元素中定义“编程语言类”和“办公软件类”两个类别选项卡。在选项卡下方定义动态组件,将数据对象中的current属性绑定到<component>元素的is属性。代码如下:
<div id="app">
    <div class="tabs">
        <div class="top">
            <ul class="tab">
                <li :class="{active: active}" @mouseover="toggleAction('program')">编程语言类</li>
                <li :class="{active: !active}" @mouseover="toggleAction('office')">办公软件类</li>
            </ul>
        </div>
        <component :is="current" :programbook="programbook" :officebook="officebook"></component>
    </div>
</div>
  1. 编写CSS代码,为页面元素设置样式。具体代码如下:
<style>
    *{
        margin: 0;
        padding: 0;
    }
    body{
        font-family: 微软雅黑;
    }
    .tabs{
        width: 320px;
        margin: 20px auto;
    }
    .top{
        height: 36px;
        line-height: 36px;
    }
    ul.tab{
        display: inline-block;
        list-style: none;
    }
    ul.tab li{
        margin: 0;
        padding: 0;
        float: left;
        width: 100px;
        height: 36px;
        line-height: 36px;
        font-size: 16px;
        cursor: pointer;
        text-align: center;
    }
    ul.tab li.active{
        display: block;
        width: 100px;
        height: 36px;
        line-height: 36px;
        background-color: #66CCFF;
        color: #FFFFFF;
        cursor: pointer;
    }
    .main{
        clear: both;
        margin-top: 10px;
    }
    .main div{
        width: 320px;
        height: 43px;
        border-bottom-width: 1px;
        border-bottom-style: dashed;
        border-bottom-color: #333333;
        background-color: #FFFFFF;
        font-size: 14px;
    }
    .main div span{
        margin-left: 10px;
    }
    .main div span:last-child{
        float: right;
        margin-right: 10px;
    }
</style>
  1. 创建根组件实例,在实例中定义数据和组件,应用components选项注册两个局部组件,组件名称分别是program和office。代码如下:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                active: true,
                current: 'program',
                programbook: [
                    {name: 'Vue.js', category: 'Vue.js'},
                    {name: 'javascript.js', category: 'javascript.js'},
                    {name: 'C++', category: 'C++'},
                    {name: 'Java', category: 'Java'},
                    {name: 'Go', category: 'Go'},
                ],
                officebook: [
                    {name: 'Word', category: 'Word'},
                    {name: 'Excel', category: 'Excel'},
                    {name: 'PPT', category: 'PPT'},
                ]
            }
        },
        methods: {
            toggleAction: function(value) {
                this.current = value;
                value === 'program' ? this.active = true : this.active = false;
            }
        },
        //注册局部组件
        components: {
            program: {
                props: ['programbook'],
                template: `<div class="main">
                    <div v-for="(item, index) in programbook">
                        <span>{{++index}}</span>
                        <span>{{item.name}}</span>
                        <span>{{item.category}}</span>
                    </div>
                </div>`
            },
            office: {
                props: ['officebook'],
                template:  `<div class="main">
                    <div v-for="(item, index) in officebook">
                        <span>{{++index}}</span>
                        <span>{{item.name}}</span>
                        <span>{{item.category}}</span>
                    </div>
                </div>`
            }
        }
    });
    vm.mount('#app');
</script>

运行实例,页面中有“编程语言类”和“办公软件类”两个类别选项卡,单击不同的选项卡可以显示不同的内容,结果如图所示。

在这里插入图片描述

6.2、缓存效果

在多个组件之间进行切换的时候,有时需要保持这些组件的状态,将切换后的状态保留在内存中,以避免重复渲染。为了解决这个问题,可以用一个<keep-alive>元素将动态组件包含起来。

下面通过一个实例来说明应用元素实现组件的缓存效果。

示例:实现选项卡内容的缓存效果。

应用动态组件实现文字选项卡的切换,并实现选项卡内容的缓存效果,实现步骤如下。

  1. 定义<div>元素,并设置其id属性值为app,在该元素中定义“水果”​“蔬菜”和“主食”三个选项卡。在选项卡下方定义动态组件,使用<keep-alive>元素将动态组件包含起来。代码如下:
<div id="app">
    <div class="tab">
        <ul class="tab-nav" :class="current">
            <li class="fruit" @click="current='fruit'">水果</li>
            <li class="vegetable" @click="current='begetable'">蔬菜</li>
            <li class="staple" @click="current='staple'">主食</li>
        </ul>
        <keep-alive>
            <component :is="current"></component>
        </keep-alive>
    </div>
</div>
  1. 编写CSS代码,为页面元素设置样式。具体代码如下:
<style>
    *{
        margin: 0;
        padding: 0;
        overflow: hidden;
    }
    body{
        font-family: 微软雅黑;
    }
    .tab{
        width: 306px;
        margin: 10px;
    }
    ul{
        list-style: none;
    }
    ul.tab-nav li{
        float: left;
        background: #fefefe;
        background: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#eeeeee));
        border: 1px solid #ccc;
        padding: 5px 0;
        width: 100px;
        text-align: center;
        cursor: point;
        color: #0000FF;
    }
    .submenu{
        width: 100px;
        height: 80px;
        border-right: 1px solid #999999;
    }
    .submenu ul{
        width: 80px;
        margin: 0 auto;
    }
    .submenu li{
        width: 80px;
        height: 26px;
        line-height: 26px;
        cursor: pointer;
        font-size: 14px;
        text-align: center;
    }
    .submenu li:hover{
        background: #EEEEEE;
    }
    .sub div{
        float: left;
        display: inline-block;
        font-size: 14px;
    }
    .sub div{
        margin-right: 10px;
    }
    .fruit .fruit, .vegetable .vegetable, .staple .staple{
        border-bottom: none;
        background: #fff;
    }
    .berry .berry, .melon .melon, .citrus .citrus{
        background: #DDDDDD;
    }
    .leafy .leafy, .rhizome .rhizome, .shoot, .shoot{
        background: #DDDDDD;
    }
    .cereal .cereal, .tubers .tubers, .bean .bean{
        background: #DDDDDD;
    }
    .tab > div {
        clear: both;
        border: 1px solid #ccc;
        width: 304px;
        height: 100px;
        padding-top: 20px;
        text-align: center;
        font-size: 14px;
        margin-top: -1px;
    }
</style>
  1. 创建根组件实例,在实例中定义数据和组件,应用components选项注册三个局部组件,组件名称分别是fruit、vegetable和staple,在每个组件中再分别定义三个子组件。代码如下:
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script type="text/javascript">
    const vm = Vue.createApp({
        data() {
            return {
                current: 'fruit'
            }
        },
        //注册局部组件
        components: {
            fruit: {
                data: function() {
                    return {
                        subcur: 'berry'
                    }
                },
                template: `<div class="sub">
                    <div class="submenu">
                        <ul :class="subcur">
                            <li class="berry" @click="subcur='berry'">浆果类</li>
                            <li class="melon" @click="subcur='melon'">瓜果类</li>
                            <li class="cirtus" @click="subcur='cirtus'">柑橘类</li>
                        </ul>
                    </div>
                    <component :is="subcur"></component>
                </div>`,
                components: {
                    berry: {
                        template: `<div>葡萄、猕猴桃、树莓</div>`
                    },
                    melon: {
                        template: `<div>西瓜、哈密瓜、甜瓜</div>`
                    },
                    cirtus: {
                        template: `<div>葡萄柚、甜橙、柠檬</div>`
                    }
                }
            },
            vegetable: {
                data: function() {
                    return {
                        subcur: 'leafy'
                    }
                },
                template: `<div class="sub">
                    <div class="submenu">
                        <ul :class="subcur">
                            <li class="leafy" @click="subcur='leafy'">叶菜类</li>
                            <li class="rhizome" @click="subcur='rhizome'">根茎类</li>
                            <li class="shoot" @click="subcur='shoot'">芽苗类</li>
                        </ul>
                    </div>
                    <component :is="subcur"></component>
                </div>`,
                components: {
                    leafy: {
                        template: `<div>大白菜、生菜、菠菜</div>`
                    },
                    rhizome: {
                        template: `<div>萝卜、蒜薹、韭菜薹</div>`
                    },
                    shoot: {
                        template: `<div>黄豆芽、绿豆芽、豆苗</div>`
                    }
                }
            },
            staple: {
                data: function() {
                    return {
                        subcur: 'cereal'
                    }
                },
                template: `<div class="sub">
                    <div class="submenu">
                        <ul :class="subcur">
                            <li class="cereal" @click="subcur='cereal'">谷类</li>
                            <li class="tubers" @click="subcur='tubers'">薯类</li>
                            <li class="bean" @click="subcur='bean'">杂豆类</li>
                        </ul>
                    </div>
                    <component :is="subcur"></component>
                </div>`,
                components: {
                    cereal: {
                        template: `<div>小麦、稻米、玉米</div>`
                    },
                    tubers: {
                        template: `<div>红薯、紫薯、山药</div>`
                    },
                    bean: {
                        template: `<div>红小豆、绿豆、芸豆</div>`
                    }
                }
            }
        }
    });
    vm.mount('#app');
</script>

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值