02 Vue的总结(组件化开发)

第二章


一、组件化开发

  • 组件化开发是Vue的重要思想

1.注册组件的步骤

  1. 创建组件构造器

  2. 注册组件

  3. 使用组件。

02 Vue的总结(组件化开发)_第1张图片

  • 提高复用

2.小小案例


<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
    <style>

    style>
head>

<body>
    <div id="vue">
        <my-con>my-con>


    div>
body>
<script>
    // 组件的注册
    const con = Vue.extend({
        template: `
            

我是组件

`
}); // 注册组件 Vue.component('my-con', con); const app = new Vue({ el: '#vue', data() { return { msg: '', } }, methods: { }, });
script> html>

3.全局组件与局部组件

  • 注册组件(全局组件,意味着可以在多个Vue的实例下面使用)
    // 组件的注册
    const con = Vue.extend({
        template: `
            

我是组件

`
}); // 注册组件 Vue.component('my-con', con);

疑问:那怎么样是注册局部组件呢?

<script>
    // 组件的注册
    const con = Vue.extend({
        template: `
            

我是组件

`
}); const app = new Vue({ el: '#vue', data() { return { msg: '', } }, methods: { }, // 注册局部组件 components: { cpn: con } }); </script>

这样就可以在单个Vue实例中注册了,不影响其他页面效果。

4.父组件与子组件的区分

  • 子组件一定要在父组件的上面,不然可能会存在问题,甚至报错。

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
    <div id="vue">
        <cpn>cpn>
    div>
body>
<script>
    // 创建子组件
    const cn = Vue.extend({
            template: `
        

我是子组件

`
}) // 父组件的注册 const con = Vue.extend({ template: `

我是组件

`
, components: { cn1: cn } }); const app = new Vue({ el: '#vue', data() { return { msg: '', } }, methods: { }, // 注册局部组件 components: { cpn: con } });
script> html>

5.注册组件的语法糖

  • 注册全局组件
   // 注册全局组件
    Vue.component('my-con', {
        template: `
            

我是组件

`
});
<my-con>my-con>

就不用再写extend属性来创建一个构造器了

那局部组件怎么创建呢?

  • 同理:局部组件只需要在局部的vue实例中来写就可以了
const app = new Vue({
        el: '#vue',
        data() {
            return {
                msg: '',

            }
        },
        methods: {

        },
        // 注册局部组件
        components: {
            cpn: {
                template: `
                

我是局部组件

`
} } });

这样就方便我们不用上下去寻找我们到底用到那个组件了,比较一目了然。

5.组件模板抽离的方法

  • 第一种方法,用标签。
<script type="text/x-template" id="con">
    <div>
        <h1>我是组件</h1>
    </div>
</script>
   Vue.component('my-con', {
        template: '#con'
    });
  • 第二种方式是采用template标签的方式。
    <template id="cn">
        <div>
            <h1>我是组件2h1>
        div>
    template>
    Vue.component('my-con', {
        template: '#cn'
    });

二、为什么组件data必须是函数

1.组件能不能访问Vue实例的数据

答案是: 不能

  • 组件是一个单独功能模块的封装:

    • 这个模块有属于自己的HTML模板,也应该有属性自己的数据data
  • 组件中的数据是保存在哪里呢?顶层的Vue实例中吗?

    • 我们先来测试一下 ,组件中能不能直接访问Vue实例中的data
<body>
    <div id="vue">
        <my-con></my-con>
        <cpn></cpn>

    </div>
    <template id="cn">
        <div>
            <h1>{{msg}}</h1>
        </div>
    </template>
</body>
<script>
    // 注册全局组件
    Vue.component('my-con', {
        template: '#cn'
    });
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                msg: '你好!!',

            }
        },
    });
</script>
  • 我们发现不能访问,而且即使可以访问,如果将所有的数据都放在Vue实例中, Vue实例就会变的非常臃肿。
  • 结论: Vue组件应该有自己保存数据的地方。

2.组件自身的属性

  1. 在前面的学习中我们知道组件component中有template属性,也有

conponents属性,同时也有一个自身储存数据的属性。就是data属性。(也有methods属性···)

注意:组件的data必须是函数类型,而且这个函数要求返回一个对象

    // 注册全局组件
    Vue.component('my-con', {
        template: '#cn',
        components:{

        },
        // data属性(必须是这个格式的)
        data() {
            return {
                
            }
        },
        // 方法属性
        methods: {
            
        },
        // ·····
    });

那么为什么是data函数?

组件是可复用的vue实例,一个组件被创建好之后,就可能被用在各个地方,而组件不管被复用了多少次,组件中的data数据都应该是相互隔离,互不影响的,基于这一理念,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,如下面这个例子:

<template>
	<div class="title">
		<h1>按钮被点击了{{ count }}</h1>
		<button v-on:click="count++">点击</button>
	</div>
</template>
<script>
	export default {
	  name: 'BtnCount',
	  data () {
	    return {
	      count: 0
	    }
	  }
	}
</script>
<style scoped>
	.title {
		background-color: red
	}
</style>

该组件被复用了三次,但每个复用的地方组件内的count数据相互不受影响,它们各自维护各自内部的count

能有这样效果正是因为上述例子中的data不是一个单纯的对象,而是一个函数返回值的形式,所以每个组件实例可以维护一份被返回对象的独立拷贝,如果我们将上述例子中的data修改为:

data : {
   count: 0
 }

那么就会造成无论在哪个组件里改变了count值,都会影响到其他两个组件里的count

这是因为当data如此定义后,这就表示所有的组件实例共用了一份data数据,因此,无论在哪个组件实例中修改了data,都会影响到所有的组件实例。

三、父子组件的通信

02 Vue的总结(组件化开发)_第2张图片

1.父传子props

  • 在上一个小节中,我们提到了子组件是不能引用父组件或者Vue实例的数据的。

  • 但是,在开发中,往往一些数据确实需要从上层传递到下层:

    • 比如在一个页面中,我们从服务器请求到了很多的数据。
    • 其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
    • 这个时候,并不会让子组件再次发送一个网络请求 ,而是直接让大组件(父组件)将数据传递给小组件(子组件)。
  • 如何进行父子组件间的通信呢?Vue官方提到

    • 通过props向子组件传递数据
    • 通过事件向父组件发送消息

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
    <div id="vue">
        <cpn :cmovies='movies'>cpn>
    div>
    <template id="cn">
        <div>
            {{cmovies}}
        div>
    template>
body>
<script>
    const cpn = {
        template: '#cn',
        props: ['cmovies'],
        data() {
            return {

            }
        },
    }
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                msg: '你好!!',
                movies: ['海王', '海贼王', '还上网吗?']
            }
        },
        methods: {

        },
        // 注册局部组件
        components: {
            cpn
        }
    });
script>
html>

2.props数据验证

  • 在前面,我们的props选项是使用一个数组。

  • 我们说过,除了数组之外,我们也可以使用对象,当需要对props进行类型等验证时,就需要对象写法了。

  • 验证都支持哪些数据类型呢?

    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol
  • 当我们有自定义构造函数时,验证也支持自定义的类型

props: ['cmovies','books'],
props:{
            cmovies:Array,
            books:Number
        },
props:{
// 1. 类型限制
// cmovies: Array,
// cmessage: string,
// 2.提供一些默认值
cmessage: {
    type: string ,
    default: 'aaaavaaa'
	}
}

3.props驼峰命名标识

再复习一下父传子的代码


<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
    <div id="vue">
        {{msg}}
        <cpn :cinfo="info">cpn>
    div>
    <template id="cn">
        <div>
            {{cinfo}}
        div>
    template>
body>
<script>
    const cpn = {
        template: '#cn',
        props: {
            cinfo: Object,
            default () {
                return {}
            }
        },
    }
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                msg: '你好!!',
                info: {
                    name: '木木',
                    age: 18,
                    sex: '男'
                }
            }
        },
        components: {
            cpn
        },
    });
script>
html>
  • 它是不支持驼峰的,再实际使用中不能将cinfo变成cInfo,如果使用
 <cpn :cinfo="info"></cpn>
//必须变成==>
 <cpn :c-info="info"></cpn>

4.子传父(自定义事件)

  • 子传父的时候是用$emit传数据的,切记当传数据的时候,自定义事件的方法是不用写()的,写了就报错了(目前是这样!!!)

<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js">script>
head>
<body>
    
    <div id="vue">
        
        <cpn @btnclick="sonclick">cpn>
    div>
    
    <template id="cn">
        <div>
            
            <button v-for="(item, index) in categories" :key="index" @click="btnclick(item.name)">
                {{item.name}}
            button>
        div>
    template>
body>
<script>
    // 子组件
    let cpn = {
        template: '#cn',
        data() {
            return {
                categories: [{
                    id: 'aaa',
                    name: "热门推荐"
                }, {
                    id: 'bbb',
                    name: "手机家电"
                }, {
                    id: 'ccc',
                    name: "电脑办公"
                }, {
                    id: 'ddd',
                    name: "美图秀秀"
                }]
            }
        },
        methods: {
            btnclick(item) {
                console.log(item);
                // 将这个结果,传给父组件
                this.$emit("btnclick", item);
            }
        },
    }

    const app = new Vue({
        el: '#vue',
        data() {
            return {
                msg: '你好!!',
                info: {
                    name: '木木',
                    age: 18,
                    sex: '男'
                }
            }
        },
        methods: {
            sonclick: function(item) {
                console.log("子组件传来了数据", item);
            }
        },
        components: {
            cpn
        },
    });
script>
html>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZbaeQ58-1604748969299)(F:\vue的终极总结\第二章\image-20201106202034167.png)]

5.组件通信-父子组件通信的案例

  • 当我们向双向绑定父传子的数据时,我们不能直接用props里的数据进行开发,虽然可以运行出我们想要的结果,但是vue官方不推荐我们这样做,会报出异常,影响我们的开发。
  • 我们需要在组件的模块中定义data函数,在data函数中进行双向绑定。
<body>
    
    <div id="vue">
        
        <cpn :dname1="name" :bname1="name1">cpn>
    div>
    
    <template id="cn">
        <div>
            
            <h1>{{dname1}}h1>
            <input type="text" v-model="dname1"/>
            <h1>{{bname1}}h1>
            <input type="text" v-model="bname1"/>
        div>
    template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: 0,
                name1: 1
            }
        },
        components: {
            cpn: {
                template: '#cn',
                props: {
                    dname1: Number,
                    bname1: Number
                }
            }
        },
    });
script>
html>
  • 我们要做一下优化

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" integripty>script>
head>

<body>
    
    <div id="vue">
        
        <cpn :dname1="name" :bname1="name1" @num1change='num1change' @num2change='num2change'>cpn>
    div>
    
    <template id="cn">
        <div>
            <h1>props:{{dname1}}h1>
            <h1>data:{{dataDName1}}h1>
            
            
            <h1>props:{{bname1}}h1>
            <h1>data:{{dataBName1}}h1>
            
            
        div>
    template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: 0,
                name1: 1
            }
        },
        methods: {
            num1change: function(event) {
                // 默认传值是string类型,需要我们在转换
                this.name = Number(event);
            },
            num2change: function(event) {
                this.name1 = Number(event);
            }
        },
        components: {
            cpn: {
                template: '#cn',
                props: {
                    dname1: Number,
                    bname1: Number
                },
                data() {
                    return {
                        dataDName1: this.dname1,
                        dataBName1: this.bname1
                    }
                },
                methods: {
                    father1: function(event) {
                        this.dataDName1 = event.target.value;
                        this.$emit('num1change', this.dataDName1)
                    },
                    father2: function(event) {
                        this.dataBName1 = event.target.value;
                        this.$emit('num2change', this.dataBName1)
                    }
                },
            }
        },
    });
script>

html>
  • 也可以用watch属性哦!!!

6.父访子children和refs的用法与比较

  • 可以通过$children的方法来调用子组件的方法。
  • 一般不用这个方法用来实际的开发。
  • 缺点:不能确定子组件的下标值是一成不变的,如果我们插入的值就不能保证我们的正确性。

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" integripty>script>
head>

<body>
    
    <div id="vue">
        
        <cpn :dname1="name" :bname1="name1">cpn>
        <button @click="child">父访问button>
    div>
    
    <template id="cn">
        <div>
            <h1>{{dname1}}h1>  
            <h1>{{bname1}}h1>    
        div>
    template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: 0,
                name1: 1
            }
        },
        methods: {

            // 父访子
            child: function() {
                console.log(this.$children);
                console.log(this.$children[0].father1())
            }
        },
        components: {
            cpn: {
                template: '#cn',
                props: {
                    dname1: Number,
                    bname1: Number
                },
                methods: {
                    father1: function() {
                        console.log("123123")

                    },

                },
            }
        },
    });
script>

html>
  • 通过refs访问子组件**【重要】**

  • refs是一个对象类型。他需要我们在我们的子组件上加入一个ref属性。我们就可以通过这个唯一的ID来获取我们对应的子组件,就规避了前面我们所说的内容


<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" integripty>script>
head>

<body>
    
    <div id="vue">
        
        <cpn :dname1="name" :bname1="name1" ref="aaaa">cpn>
        <button @click="child">父访问button>
    div>
    
    <template id="cn">
        <div>
            <h1>{{dname1}}h1>  
            <h1>{{bname1}}h1>    
        div>
    template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: 0,
                name1: 1
            }
        },
        methods: {

            // 父访子
            child: function() {
                console.log(this.$refs);
				 console.log(this.$refs.aaa.father1());
            }
        },
        components: {
            cpn: {
                template: '#cn',
                props: {
                    dname1: Number,
                    bname1: Number
                },
                methods: {
                    father1: function() {
                        console.log("123123")

                    },

                },
            }
        },
    });
script>
html>

7.子访问父 parent和root用法和比较

  • 实际开发中使用的很少,因为代码要减少耦合性!!!

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" integripty>script>
head>

<body>
    
    <div id="vue">
        <cpn>cpn>
    div>
    
    <template id="cn">
        <div>
            <h1>{{name}}h1>  
            <button @click="parent">子访问父组件button>
        div>
    template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: '我叫父亲'
            }
        },
        methods: {
            father: function() {
                console.log("我等我儿子吊我");
            }
        },
        components: {
            cpn: {
                template: '#cn',
                data() {
                    return {
                        name: "我是子组件"
                    }
                },
                methods: {
                    parent: function() {
                        console.log(this.$parent);
                        console.log(this.$parent.name);
                        console.log(this.$parent.father());

                    },

                },
            }
        },
    });
script>
html>
  • root是直接访问Vue实例

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" integripty>script>
head>

<body>
    
    <div id="vue">
        <cpn>cpn>
    div>
    
    <template id="cn">
        <div>
            <h1>{{name}}h1>  
            <button @click="parent">子访问父组件button>
        div>
    template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: '我叫父亲'
            }
        },
        methods: {
            father: function() {
                console.log("我等我儿子吊我");
            }
        },
        components: {
            cpn: {
                template: '#cn',
                data() {
                    return {
                        name: "我是子组件"
                    }
                },
                methods: {
                    parent: function() {
                        console.log(this.$root);
                        console.log(this.$root.name);
                        console.log(this.$root.father());

                    },

                },
            }
        },
    });
script>

html>

四、插槽

1.为什么使用slot

  • slot翻译为插槽:

    • 在生活中很多地方都有插槽,电脑的USB插槽,插板当中的电源插槽。
    • 插槽的目的是让我们原来的设备具备更多的扩展性
    • 比如电脑的USB我们可以插入U盘、硬盘、手机、音响、键盘、鼠标等等。
  • 组件的插槽:

    • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
    • 让使用者可以决定组件内部的一些内容到底展示什么。
  • 栗子:移动网站中的导航栏。

    • 移动开发中,几乎每个页面都有导航栏。
    • 导航栏我们必然会封装成一个插件 ,比如nav-bar组件。
    • 一旦有了这个组件,我们就可以在多个页面中复用了。

<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" integripty>script>
head>

<body>
    <div id="vue">
        <p>{{name}}p>
        <cpn><i>插槽的作用i>cpn>
        <cpn><button>
            我又变成一个按钮哩!!
            button>cpn>
    div>
    <template id="cn">
    <div>
        <h1>我是子组件h1>
        <slot><i>我是默认值。可以不谢哦!!i>slot>
    div>
template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: '李玉森'
            }
        },
        components: {
            cpn: {
                template: '#cn'
            }
        }
    });
script>
html>
  • 了解了为什么用slot ,我们再来谈谈如何使用slot ?

    1. 在子组件中,使用特殊的元素就可以为子组件开启一个插槽

    2. 该插槽插入什么内容取决于父组件如何使用。

  • 我们通过一个简单的例子,来给子组件定义一个插槽。

    1. 中的内容表示,如果没有在该组件中插入任何其他内容,就默认显示该内容
    2. 有了这个插槽后,父组件如何使用呢?

2.具名插槽

  1. 引子
    <div id="vue">
        <cpn>cpn>
    div>
    <template id="cn">
    <div>
        <slot><i>我是走左边的i>slot>
        <slot><i>我是走中间的i>slot>
        <slot><i>我是走右边的i>slot>
    div>
template>

这样的话我们如果就想改中间的插槽的格式,那么浏览器就不知道到底是哪个要改,如果写一个,那么实际效果就全改了,所以我们就要给我们的插槽起名字


<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Documenttitle>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js" integripty>script>
head>

<body>
    <div id="vue">
        <cpn><span slot="mid">我改的是中间的是吧span>cpn>
    div>
    <template id="cn">
    <div>
        <slot name="left"><i>我是走左边的i>slot>
        <slot name="mid"><i>我是走中间的i>slot>
        <slot name="right"><i>我是走右边的i>slot>
    div>
template>
body>
<script>
    const app = new Vue({
        el: '#vue',
        data() {
            return {
                name: '李玉森'
            }
        },
        components: {
            cpn: {
                template: '#cn'
            }
        }
    });
script>

html>

你可能感兴趣的:(Vue,vue)