目 录CONTENT

文章目录

vue组件化开发

Administrator
2021-11-18 / 0 评论 / 1 点赞 / 5481 阅读 / 45539 字

vue组件化开发

在现行的MVVM数据驱动页面的框架里面(vue/react/angular),它们都是支持组件化开发的,组件化开发也叫虚拟dom(virtual-dom)开发

一、存在的问题

<body>
    <div id="app">
        <ul class="list-group">
            <li>张三</li>
            <li>李四</li>
            <li>王五</li>
        </ul>
        <ul class="list-group">
            <li>张三</li>
            <li>李四</li>
            <li>王五</li>
        </ul>
        <ul class="list-group">
            <li>张三</li>
            <li>李四</li>
            <li>王五</li>
        </ul>
    </div>
</body>

我们平常在做开发的时候,有时候发现页面上面有很多布局或结构都是相同的时候,我们不得不把这些代码都写一次,然后复制粘贴 ,这样代码会变得非常多

image.png

这个时候代码的冗余量会非常大。按照我们之前的思路,我们会想到封装。现在的问题就是HTML是否可以封装?

二、关于virtual DOM

在上一个章节,我们其实已经得到了一个问题,如果HTML标签要是能够 封装,那就可以解决这个问题,现在的vue框架就可以实现这样效果

在vue里面,有两个核心点

  1. 数据驱动页面
  2. 虚拟DOM(virtual-dom)开发,这一种开发就是组件化的开发

到底什么是virutal-dom,我们现在来尝试一下

<body>
    <div id="app">
        <user-list></user-list>
        <hr>
        <user-list></user-list>
        <hr>
        <user-list></user-list>
    </div>


    <!-- template不能在托管区域里面 -->
    <template id="temp1">
        <ul class="list-group">
            <li>张三</li>
            <li>李四</li>
            <li>王五</li>
        </ul>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    Vue.component("user-list",{
        template:"#temp1"
    })
    new Vue({
        el:"#app",
        data:{
            
        }
    })
</script>

这个时候我们通过上面的代码就已经实现之前对HTML的封装

代码说明

  1. 要把所有封装的html放在<template>的标签里面,并且定义一个不重复的id
  2. 在JS里面要通过Vue.component()这个方法来注释一个组件,名叫user-list的虚拟标签
  3. 在HTML的页在上面通过使用<user-list></user-list>来调用这个组件

这个时候我们就可以看到,在网页当中本身是没有<user-list>这一个标签的,但是现在又有了这个标签,所以这个标签应该算是一个虚拟的标签,这种技术就叫virtual-dom开发,也就是组件化开发

在前端的三大框架里都支持组件化的virtual-dom开发,但是都有一些共同的特点

注意事项

  1. 组名的名称(virutal-dom的名称)不能是html里面已经存在的标签名称
  2. 组个名称如果是驼峰命名的,则需要进行转义操作,如userList就会转义成<user-list>
  3. template不能写在vue的托管区域里面
  4. template标签下面是不支持片段代码的,通俗一点就是只能有一个根标签

标哥特别提醒:vue的组件其实可以看成是一个小型的vue

三、全局组件

顾名思义全局组件就是可以在全局范围内使用的

全局组件使用Vue.component这个方法来实现的,上一个章节的内部就是全局组件

  1. 先准备一个<tempalte>标签,然后设置一个id,把要封装的html写在里面去

    <template id="temp1">     
    </template>
    

    当然上面的<template>也可以换成<script type="text/template" id="temp1">

    千万千万记得,不能放在托管区域里在,也不能有片段代码

  2. 在js代码当调调用Vue.component方法,把之前定义的HTML代码变成是一个组件

    Vue.component("c", {
        template: "#temp1"
    });
    

📚 案例

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>全局组件</title>
    <style>
        .box{
            border: 1px solid black;
            margin: 10px;
        }
    </style>
</head>

<body>
    <div id="app">
        <one></one>
    </div>
    <template id="temp1">
        <div class="box">
            <h2>我是第一个组件</h2>
            <two></two>
            <three></three>
        </div>
    </template>
    <template id="temp2">
        <div class="box">
            <h2>我是第二个组件</h2>
            <three></three>
        </div>
    </template>
    <template id="temp3">
        <div class="box">
            <h2>我是第三个组件</h2>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
   
    Vue.component("one",{
        template:"#temp1"
    });
    Vue.component("two",{
        template:"#temp2"
    });
    Vue.component("three",{
        template:"#temp3"
    });

    new Vue({
        el: "#app"
    })
</script>

</html> 

image.png

上面就是一个最典型的全局组件的案例,全局组件只要注册成功,就可以在任何范围内使用

四、局部组件

顾名思义就是只可以在局部范围内使用,这一点正好是与全局组件相反

在学习局部组件的时候,请同样一定记住一句话vue的组件其实可以看成是一个小型的vue

  1. 编写一个<template>的区域,这一点与全局组件相同

    <template id="temp1">
        <div>
            <h2>这是一个组件</h2>
        </div>
    </template>
    
  2. 创建一个对象,指定组件的接管区域【这一点与vue很像,vue是通过el属性去接管,而组件是通过tempalte属性接管】

     var one = {
         template: "#temp1"
     }
    

    当上面的代码完成了以后,它还仅仅只是一个对象

  3. 要在哪里使用就在哪里把这个对象注册成一个组件

    new Vue({
        el: "#app",
        components:{
            userList:one
        }
    })
    

局部组件本身是一个对象,在使用之前一定要注册

📕 案例


<body>
    <div id="app">
        <one></one>
    </div>
    <template id="temp1">
        <div class="box">
            <h2>第一个组件</h2>
            <two></two>
            <three></three>
        </div>
    </template>
    <template id="temp2">
        <div class="box">
            <h2>第二个组件</h2>
            <three></three>
        </div>
    </template>
    <template id="temp3">
        <div class="box">
            <h2>第三个组件</h2>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    
    var three = {
        template:"#temp3"
    };

    var two = {
        template:"#temp2",
        components:{
            three
        }
    };

    var one = {
        template:"#temp1",
        components:{
            two,three
        }
    };
    new Vue({
        el: "#app",
        components:{
            one
        }
    })
</script>

image.png

局部组件在使用之前一定要事先注册一下,而注册的方法很简单,把封装组件的对象放在components属性下面即可

五、组件中的数据

一个组件其实就是一个小型的vue,组件的内部也是可以有数据,也是可以有方法,也是可以有事件等

vue的组件与组件之前,及vue的内部都是相互独立,相互隔离,默认不进行相互通信,通过下面的例子可以看到

1. 组件内部的事件

<body>
    <div id="app">
        <button type="button" @click="sayHello">外边的铵钮</button>
        <hr>
        <one></one>
    </div>
    <template id="temp1">
        <div>
            <button type="button" @click="sayHello">按钮</button>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    var one = {
        template: "#temp1",
        methods:{
            sayHello(){
                alert("我是内部的方法")
            }
        }
    }
    new Vue({
        el: "#app",
        methods: {
            sayHello() {
                alert("我是外边的方法")
            }
        },
        components: {
            one
        }
    })
</script>

在上面的代码里面, 我们可以看到,组件内部的也是可以有方法的,它的方法和vue里面的方法是一样的

2. 组件自身数据

graph TD A[组件的数据]-->B(内部自己的数据) A-->C(外部给我的数据)

image.png

如果一个组件的内部需要数据是可以自己产生的,这一点与vue很像,但是语法上面又有一些区别 ,它内部也有一个data属性,但组件内部的data是一个方法,这个方法所返回的对象才是真正的组件数据

<body>
    <div id="app">
        <h2>{{userName}}</h2>
        <hr>
        <one></one>
    </div>
    <template id="temp1">
        <div>
            <h3>组件内的姓名:{{stuName}}</h3>
            <ul>
                <li v-for="(item,index) in list" :key="index">{{item}}</li>
            </ul>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    var one = {
        template: "#temp1",
        data(){
            return {
                stuName:"陈欢欢",
                list:["a","b","c","d"]
            }
        }
    }
    new Vue({
        el: "#app",
        data:{
            userName:"张三"
        },
        components: {
            one
        }
    })
</script>

3. 组件接收外部数据


<body>
    <div id="app">
        <list-box :aaa="['a','b','c']"></list-box>
    </div>
    <template id="temp1">
        <div>
            {{aaa}}
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    // 数据要么在自身,要么从外边拿
    let listBox = {
        template: "#temp1",
        props:{
            aaa:{
                type:Array,
                required:true
            }
        },
        data() {
            return {
                
            }
        }
    }
    new Vue({
        el: "#app",
        data: {
        },
        components: {
            listBox
        }
    })
</script>

在上面的代码当中,我们可以看到,组件内部有一个数据aaa是从外边获取到的数据

在组件的内部通过props来接收传递过来的值,type表示接收的数据类型,requierd表示这个值必须要传给我

在接收值的时候其实也是有两种语法的,一种是对象语法,一种是数组语法

对象语法

props:{
    aaa:{
        type:类型,
        required:boolean,
        default(){
            return 默认值
        }
    },
    bbb:{
        type:类型....
    }
}

数组语法

props:["aaa","bbb"]

注意事项

无论是对象语法还是数组语法,在使用属性的的时候,都要注意驼峰命令的情况,如果属性非要使用驼峰命名请转义, 如<c :user-list="['a','b']"></c>,则在在接收的时候就可以使用props:["userList"]


<body>
    <div id="app">
        <list-box :list-info="stuList"></list-box>
        <hr>
        <list-box :list-info="teacherList"></list-box>
    </div>
    <template id="temp1">
        <ul class="list-group">
            <li v-for="(item,index) in listInfo" :key="index">
                {{item}}
            </li>
        </ul>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    // 数据要么在自身,要么从外边拿
    let listBox = {
        template: "#temp1",
        props: {
            listInfo:{
                type:Array,
                required:true
            }
        }
    }
    new Vue({
        el: "#app",
        data: {
            stuList: ["季强", "陈欢欢", "喻金", "松明"],
            teacherList: ["桃子", "标哥"]
        },
        components: {
            listBox
        }
    })
</script>

六、组件的事件及方法

之前已经给同学样讲过了,每个组件都是相互隔离的,如果要相互之间使用数据,则必须要进行传递,外部的数据传递到组件内部,这样就会形成一种关系叫父子关系,A使用了B,则我们就认为A是B的父级

image.png

1. 父级组件调用子级组件的方法【第一种方式】

在组件的部是两个特殊的属性的

  • $parent它指向自己的父级组件
  • $children它包含当前组里面所使用的所有子组件

案例


<body>
    <div id="app">
        <button type="button" @click="outer1">外部的按钮</button>
        <one></one>
    </div>
    <template id="temp1">
        <div class="box">
            <button type="button" @click="aaa">内部的按钮</button>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    let one = {
        template: "#temp1",
        methods: {
            aaa() {
                alert("我是内部的按钮的事件");
                console.log("我的父级",this.$parent);
            }
        }
    }
    new Vue({
        el: "#app",
        methods:{
            outer1(){
                alert("我是外部的东西");
                //想在这里调用里面的aaa怎么办?
                this.$children[0].aaa();
            }
        },
        components:{
            one
        }
    })
</script>

在上面的案例里面,我们可以通过$children来找到所使用了的所有子组件

上面的代码里面是极其不严格的,因为它是通过$children[索引]来找到子组件的,如果子组件的索引不对,则一切白费

2. 父级组件调用子级组件的方法【第二种方式】

子级组件其实也可以看成是一个dom,只是它是一个虚拟dom,而vue支持dom操作

<body>
    <div id="app">
        <button type="button" @click="outer1">外部的按钮</button>
        <one ref="ttt"></one>
        <h2 ref="ggg">这是一个真实的DOM</h2>
    </div>
    <template id="temp1">
        <div class="box">
            <button type="button" @click="aaa">内部的按钮</button>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    let one = {
        template: "#temp1",
        methods: {
            aaa() {
                alert("我是内部的按钮的事件");
            }
        }
    }
    new Vue({
        el: "#app",
        methods:{
            outer1(){
                alert("我是外部的东西");
                //想在这里调用里面的aaa怎么办?
                //虚拟dom来操作
                this.$refs["ttt"].aaa();
            }
        },
        components:{
            one
        }
    })
</script>

在上面的代码里面,我们可以使用ref的方式来操作,

七、数据流的单向性

现有如下案例

<body>
    <div id="app">
        <h2>我是{{userName}},我有一个女儿叫{{daughterName}}</h2>
        <hr>
        <aa :daughter-name="daughterName"></aa>
    </div>
    <template id="temp1">
        <div class="box">
            <h2>我是女儿,我的名子叫:{{daughterName}}</h2>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    var aa = {
        template: "#temp1",
        props: ["daughterName"]
    }
    new Vue({
        el: "#app",
        data: {
            userName: "标哥哥",
            daughterName: "杨柳彤"
        },
        components: {
            aa
        }
    })
</script>

在上面的案例里面,我们向子级组件传递了一个数据:daughter-name="daughterName",子级组件拿到了以后就显示在了页面上面,所以数据是由父级组件传递子级组件

graph LR A[父级组件]-->|传递|B[子级组件]

image.png

image-20211111170204842

现有2个问题,

  • 父级组件如果改变了daughterName这个数据,子级是否会改变?
  • 子级能不能改变父级传递过来的daughterName

结论一:父级改变,子级也会改变,具体看下面代码

<div id="app">
    <h2>我是{{userName}},我有一个女儿叫{{daughterName}}</h2>
    <button type="button"
            style="background-color: red;"
            @click="daughterName='杨妞'">
        父级在改
    </button>
    <hr>
    <aa :daughter-name="daughterName"></aa>
</div>

在上面的代码里面,我们添加啊一个按钮,在这个按钮里绑定事件,改变当前vue里面的daughterName,结果发现子级组件也改了

结论二:子级不能更改父级传递过来的数据,一旦更改就会报错,破坏了数据的统一,具体看下面代码

<template id="temp1">
    <div class="box">
        <h2>我是女儿,我的名子叫:{{daughterName}}</h2>
        <button type="button" @click="daughterName='爱莎公主'">子级要改变名子</button>
    </div>
</template>

在组件内部的模板里面,改了外部传递过来的数据,则会报错

在上面的两个结论里面,我们可以到这就是数据流的单向性,只能由父级到子级,不能由子级到父级

八、破坏数据流单向性

正常情况下,我们是不需要破坏数据流的单向性的,但是如果某些特殊场景需要我们去改变,这个时候也可以使用下面的两种方式

1. 利用对象的堆栈原理

vue在进行组件传值的的时候使用的是浅拷贝规则 ,所以如果我们传递的是一个基本数据类型,数据在传递以后是互不影响 的,如果要让两个数据之间有相互的关系,可以使用对象来完成,如下


<body>
    <div id="app">
        <h2>我是{{userName}},我有一个女儿叫{{obj.daughterName}}</h2>
        <button type="button"
            style="background-color: red;"
            @click="obj.daughterName = '杨妞'">
            父级在改
        </button>
        <hr>
        <aa :bbb="obj"></aa>
    </div>
    <template id="temp1">
        <div class="box">
            <h2>我是女儿,我的名子叫:{{bbb.daughterName}}</h2>
            <button type="button"
                @click="bbb.daughterName='杨爱莎公主'">
                子级要改变名子
            </button>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    var aa = {
        template: "#temp1",
        props: ["bbb"]
    }
    new Vue({
        el: "#app",
        data: {
            userName: "标哥哥",
            obj: {
                daughterName: "杨柳彤"
            }
        },
        components: {
            aa
        }
    })
</script>

image.png

2. 通过vue官方提供的方法--自定义事件

image.png

<body>
    <div id="app">
        <h1>我有一个女儿,她的名子叫{{myDaughterName}}</h1>
        <one :tname="myDaughterName"
            @dadchangemyname="changeDaughterName"></one>
    </div>
    <template id="temp1">
        <div class="box">
            <h2>我是女儿,我的名子是{{tname}}</h2>
            <button type="button" @click="childChangeName">子级改变姓名</button>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    let one = {
        template: "#temp1",
        props: ["tname"],
        methods: {
            childChangeName() {
                //触发一个自定义事件
                this.$emit("dadchangemyname","杨爱莎公主")
            }
        }
    }
    new Vue({
        el: "#app",
        data: {
            myDaughterName: "杨柳彤"
        },
        methods: {
            changeDaughterName(newName) {
                this.myDaughterName = newName;
            }
        },
        components: {
            one
        }
    })
</script>

在组件的内部触发了一个自定义的事件,然后这个自定义的事件上面通过this.$emit("事件名",参数),在调用这个组件时候,我们就可以使用@自定义事件去完成

九、自定义事件

在上面的案例当中,我们去破解数据流的单向的时候使用了自定义事件,其实在vue开发里在,自定义事件是非常频繁的。现在我们把刚刚自定义的流程先梳理一下

graph LR A["子级$emit()自定义事件"]-->B["父级监控这个@自定义事件"] B-->C[父级调用方法执行]

image.png

现在我们通过一个最基本的案例来执行操作

image.png

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>自定义事件</title>
    <style>
        .btn {
            min-width: 120px;
            height: 35px;
            border: none;
            outline: none;
            background-color: lightseagreen;
            cursor: pointer;
            color: white;
        }

        .btn:active {
            box-shadow: 0px 0px 10px 5px lightgray;
        }
    </style>
</head>

<body>
    <div id="app">
        <my-button text="登陆" @click="aaa"></my-button>
    </div>
    <template id="temp1">
        <div class="btn-box">
            <button type="button" class="btn" @click="innerClick($event)">{{text}}</button>
        </div>
    </template>

</body>
<script src="./js/vue.js"></script>
<script>
    let myButton = {
        template: "#temp1",
        props: {
            text: {
                type: String,
                default: () => "按钮"
            }
        },
        methods:{
            innerClick(event){
                this.$emit("click",event);
            }
        }
    }
    new Vue({
        el: "#app",
        methods: {
            aaa() {
                alert("hello world");
                console.log(event);
            }
        },
        components: {
            myButton
        }
    })
</script>
</html>

十、组件的插槽

1. 普通插槽

也叫default默认插槽

组件中的插槽可以理解成我们之前的电脑里同的主板,这个主板可以确定大多数的功能,对于不确定的东西则预留一个插槽,方便后期插入东西进去

这个思维方式与组件很相似,我在封装组件的时候,其实也可以一些公共的确定的HTML封装起来,对于那些不确定的东西我们则给可以给它预留一下插槽

image.png

如果想在件组件的开始标签与结束标签中插入内容,则在封装组件进修必须预留插槽

如果要想在组件内预留插槽,则要使用<slot>去完成,如下代码所示

<body>
    <div id="app">
        <one>
            <button type="button">我是按钮</button>
            <button type="button">我是按钮</button>
            <button type="button">我是按钮</button>
        </one>
    </div>
    <template id="temp1">
        <div class="box">
            <!-- 现在在这里预留了一个插口 -->
            <slot></slot>
            <h2>我是一个组件-------哈哈哈</h2>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    let one = {
        template:"#temp1"
    }
    new Vue({
        el:"#app",
        components:{
            one
        }
    })
</script>

在上面的代码里面,one的虚拟dom里面插入了3个button标签,这3个标签最终全部插入到了slot的位置

效果图:一个slot多次插入

image.png

同时还有另一种情况,一个插入点插入在多个插槽slot里面,如下代码所示

<div id="app">
    <one>
        <button type="button">我是按钮</button>
    </one>
</div>
<template id="temp1">
    <div class="box">
        <!-- 现在在这里预留了一个插口 -->
        <slot></slot>
        <h2>我是一个组件-------哈哈哈</h2>
        <slot></slot>
    </div>
</template>

在上面的代码里面,我们可以看到,一个button标签插入到了多个slot里面

效果图:一个插入放在多个slot里面

image.png

在刚刚的代码里面,我们通过slot在组件内部定义了插槽,后期就可以直接 插入,其实这些slot都叫默认插件,它的完整写法应该是下面的写法

image.png

默认情况所有的slot都必须指定一个name的,如果不指定则默认就是default

2.具名插槽

在上面的默认插槽里面,所有的slot如果在不指定name的情况都是默认插槽default,但是这个name是可以设置为其它的值的,如果设置为其它的值 ,我们就把这个插槽叫具名插槽

 <div id="app">
     <one>
         <p>我是默认的</p>
         <button type="button" slot="footer">我是另一个插入的</button>
         <a href="#" slot="inner">我是第三个插入的</a>
     </one>
</div>
<template id="temp1">
    <div class="box">
        <slot name="footer"></slot>
        <slot></slot>
        <h2>我是一个组件-------哈哈哈
            <slot name="inner"></slot>
        </h2>
        <slot name="footer"></slot>
    </div>
</template>

image.png

在上面的代码里面,我们可以看到插槽是可以指定具体的名子的,仍然可以实现一对多,多对一

当插槽有了名子以后,我们在插入的时候就可以指定插入在某一个插槽里面

  1. 我们在组件里面定义插槽的时候使用了 标签,并且在这个slot标签上面定义了 name 这 个属性,这就是具名插槽
  2. 在调用这个组件的时候,可以向指定的插槽位置插入内容,只需要在这个元素上面添加 slot="名称" 即可
  3. 具名插槽也是可以多次使用的,所以在上面的代码当中 name="footer"这个插槽就出现2 次

案例:现有下面的几种情况,同学想一下如何定义组件

image.png

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>插槽的案例</title>
    <link rel="stylesheet" href="https://at.alicdn.com/t/font_2711195_ar2lbyi6qmj.css">
    <style>
        *{
            margin: 0;
            padding: 0;
            list-style-type: none;
        }
        .page-title{
            height: 45px;
            background-color: #008de1;
            font-weight: bold;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            position: relative;
        }
        .left-back{
            position: absolute;
            left: 10px;
        }
        .right-menu{
            position: absolute;
            right: 10px;
        }
        .right-menu>.iconfont{
            margin-left: 5px;
        }
    </style>
</head>
<body>
    <div id="app">
        <app-title :show-back="true" @back="bbb">
            软帝点餐
            <span slot="right-menu" class="iconfont icon-home-fill" @click="aaa"></span>
        </app-title>
    </div>
    <template id="temp1">
        <div class="page-title">
            <div class="left-back" @click="backClick($event)" v-if="showBack">
                <span class="iconfont icon-fanhui"></span>
                返回
            </div>
            <slot></slot>
            <div class="right-menu">
               <slot name="right-menu"></slot>
            </div>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    let appTitle = {
        template:"#temp1",
        props:{
            showBack:{
                type:Boolean,
                default:()=>false
            }
        },
        methods:{
            backClick(event){
                this.$emit("back",event);
            }
        }
    }
    
    new Vue({
        el:"#app",
        components:{
            appTitle
        },
        methods:{
            aaa(){
                alert("我是aaa的方法")
            },
            bbb(event){
                alert("我是内部发射到外边的返回啊")
            }
        }
    })

</script>
</html>

3. 作用域插槽

作用域插槽是一个比较常见的现象,在工作当中使用得非常频繁,现在来看一下场景

<body>
    <div id="app">
        <one>
        	<p>我想拿到组件内部的userName</p>
        </one>
    </div>
    <template id="temp1">
        <div class="box">
            <h2>这是一个组件</h2>
            <slot></slot>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    let one = {
        template: "#temp1",
        data(){
            return  {
                userName:"小珊子"
            }
        }
    }
    new Vue({
        el: "#app",
        components: {
            one
        }
    })
</script>

我们以前的时候一直都是外部的数据传递到组件内部,如果把组件内部的数据传递到外边去呢,例如上面在上面的代码当中,如果把userName传递到外边去

组件内部的数据如果要传递到组件外部去,其实原理是一样的,仍然使用自定义的属性

image.png

在上面的案例里面,我们看到了,我们通过slot-scope拿到了插槽里在作用域,然后拿到了插槽上面所有的值,最后直接 使用

在上在贩案例里面,我们还看到了,这个些插槽都是默认的default的插槽,如果是具名插槽呢?

image.png

<body>
    <div id="app">
        <one>
            <div slot="default" slot-scope="scope">
                用户名:{{scope.userName}}
            </div>
            <hr>
            <div slot="footer" slot-scope="scope">
                年龄:{{scope.age}}
            </div>
        </one>
    </div>
    <template id="temp1">
        <div class="box">
            <slot name="default" :user-name="userName"></slot>
            <h2>这是一个组件</h2>
           <slot name="footer" :age="18"></slot>
        </div>
    </template>
</body>
<script src="./js/vue.js"></script>
<script>
    let one = {
        template: "#temp1",
        data(){
            return  {
                userName:"小珊子"
            }
        }
    }
    new Vue({
        el: "#app",
        components: {
            one
        }
    })
</script>

代码分析:

  1. slot="default"的插槽里面,我们的数据是直接向外部传送了userName,同时要注意default是默认的插槽,可以不指定名子也行
  2. slot="footer"的插槽里面,我们向外传递数据:age='18'
  3. 在调用这个组件的时候,我们可以通过slot="default"slot="footer"来决定到底插入在什么地方,同时还可以使用slot-scope拿到这个插槽的作用域(通俗一点讲就是拿到这个插槽向外部传递的数据)
  4. 主要作用的还是两个属性
    • slot决定到底插入在什么位置
    • slot-scope拿到作用域(拿到插槽上面的数据)

上面的slot-scope是过时的写法,现在新的vue2.6.1开始就使用新的语法叫v-slot

 <template id="temp1">
     <div class="box">
         <slot name="default" :user-name="userName"></slot>
         <h2>这是一个组件</h2>
         <slot name="footer" :age="18"></slot>
     </div>
</template>

旧版本的语法

<one>
    <p slot="default" slot-scope="scope">
        用户名:{{scope.userName}}
    </p>
    <p slot="footer" slot-scope="scope">
        年龄:{{scope.age}}
    </p>
</one>

新版本的语法

<one>
    <template v-slot:default="scope">
        <p>
            用户名:{{scope.userName}}
        </p>
    </template>
    <template v-slot:footer="scope">
        <p>
            年龄:{{scope.age}}
        </p>
    </template>
</one>

代码分析:

  1. v-slot就是去拿作用域,如果后面不跟具体的名称就是拿default,所以v-slot:default就是v-slot
  2. 如果要拿作用域则v-slot:插槽名称="scope"
  3. 新版本里面只能通过<template>要都会来插入,不能通过其它的标签

最新版本的语法

 <one>
     <template #default="scope">
         <h1>我在默认的位置插入:{{scope.userName}}</h1>
     </template>
     <template #footer="scope">
         <p>我在footer的位置插入:{{scope.age}}</p>
     </template>
</one>

我们推荐同学们使用第一种和第三种,但是还是要了解第二种

案例

<div id="app">
    <one>
        <template #default="{age,sex,userName}">
            <h2>插入</h2>
            <p>{{age}}</p>
            <p>{{userName}}</p>
            <p>{{sex}}</p>
        </template>
    </one>
</div>
<template id="temp1">
    <div class="box">
        <h2>这是一个组件</h2>
        <slot name="default" :age="18" :user-name="userName" sex="女"></slot>
    </div>
</template>

在新版本的语法里面,作用域是可以直接解构的

十一、vue及组件生命周期

我们都知道如果要使用vue ,则在最开始的时候就要去new一个Vue,那么在创建这个vue的对象过程当中,到底经过了哪些过程呢,这个我们就必须要掌握vue的生命周期

生命周期( lifeCycle):指从出生到死亡的过程,vue的生命周期指的是从创建到销毁的过程

在vue的内部,它的生命其实是有四个阶段

graph LR A[创建create]-->B[挂载mount]-->C[更新update]-->D[销毁]

image.png

通俗一点来讲,我们把vue的生命周期分为4个阶段,8个过程,它们在不同的状态有不同的操作,在每个阶段都会执行一个特殊的函数,这个函数叫钩子函数

钩子函数:所谓的钩子函数指的是vue在不同的周期情况下自己调用的函数

  1. beforeCreatevue创建之前
  2. createdvue创建以后
  3. beforeMountvue挂载之前【vue接管页面之前】
  4. mountevue挂载之后【vue接管页面之后】
  5. beforeUpdatevue内部更新之前
  6. updatedvue内部更新以后
  7. beforeDestoryvue销毁之前
  8. destoryedvue销毁之后

vue在一个是8个状态,对应了8个钩子函数,在不同钩子函数里面要执行不同的操作

周期名称data属性computed属性methods方法$refs操作
beforeCreate()
created()
beforeMount()
mounted()

在vue的组件内部也是这8个生命周期钩子函数,之前就给同学们讲过,组件就是一个小型的vue

1. 跨生命周期的调用

vue应该是在不同的生命周期里面执行不同的操作,所以有严格的限制条件,但是某些特殊情况下我们是需要跨生命周期调用的,这咋办呢。如下案例所示

<body>
    <div id="app">
        <h2 ref="h2"></h2>
    </div>
</body>
<script src="./js/vue.min.js"></script>
<script>
    new Vue({
        el: "#app",
        data: {
            age: 18
        },
        created() {
            console.log("我是created");
            //将原来的年龄加上一个20以内的随机数,然后设置在h2上面,但是要通过dom操作
            let newAge  = this.age + parseInt(Math.random() * 20);
            this.$refs.h2.innerText = newAge;
        },
        mounted() {
        }
    });
</script>

在上面的代码当中,我们可以看到,我们在created()生命周期里面创建了一个变量,然后要放在ref为h2的元素上面,怎么办

问题1:我不能在created()里面操作$refs

问题2:如果我在mounted()操作$refs这个时候又拿到不变量newAge

解决方案如下

new Vue({
    el: "#app",
    data: {
        age: 18
    },
    created() {
        console.log("我是created");
        //将原来的年龄加上一个20以内的随机数,然后设置在h2上面,但是要通过dom操作
        let newAge  = this.age + parseInt(Math.random() * 20);
        this.$nextTick(()=>{
            this.$refs.h2.innerText = newAge;
        });
    },
    mounted() {

    }
});

我们把需要在下一个生命周期执行的代码放在了$nextTick里面

$nextTick()官方的解释这样的,它会判断当前的代码是否可以在本生命周期执行,如果不可以会顺延到下一个生命周期,如果还不行则继续顺延,直到可以执行为止

2. v-if及v-show的区别

之前在讲vue的基础的时候我们提到了一点,v-if是通过注释的方式让元素消失了,而v-show则是通过css属性display:none来操作的,但是它的本质点在生命周期里面

v-show不会重新触发生命周期,因为本质上面是把元素隐藏了,没有销毁

<body>
    <div id="app">
        <button type="button" @click="flag = !flag">切换</button>
        <!--这里使用了v-show的操作 -->
        <one v-show="flag"></one>
    </div>
    <template id="temp1">
        <div>
            <h2>我是一个数:{{num}}</h2>
        </div>
    </template>
</body>
<script src="./js/vue.min.js"></script>
<script>
    let one = {
        template:"#temp1",
        data(){
            return {
                num:18
            }
        },
        created(){
            console.log("我是组件内部的created"+new Date().toLocaleString())
        }
    }
    new Vue({
        el: "#app",
        data:{
            flag:true
        },
        components:{
            one
        }
    });
</script>

我们同样的,把上面的代码替换成v-if来试一下

 <one v-if="flag"></one>

这个时候我们发现,每次重新显示的时候,生命周期都重新执行了,这说明每次重新显示都是重新创建了一个新的组件

3. keep-alive的使用

vue及所有的组件正常情况下都要经历上面的四个过程,从创建到销毁,但是有一个特殊情况,主是这个组件吃了长生不死药,它不死,对于这种情况,我们叫上keep-alive

在vue的内部,有一个自带的组件叫 <keep-alive>在这个组件下面的内容是不会被销毁掉的,也就是不 会执行 destory 这个过程

<body>
    <div id="app">
        <button type="button" @click="flag = !flag">切换</button>
        <keep-alive>
            <one v-if="flag"></one>
        </keep-alive>
    </div>
    <template id="temp1">
        <div>
            <h2>我是一个数:{{num}}</h2>
        </div>
    </template>
</body>
<script src="./js/vue.min.js"></script>
<script>
    let one = {
        template:"#temp1",
        data(){
            return {
                num:18
            }
        },
        created(){
            console.log("我是组件内部的created"+new Date().toLocaleString())
        },
        activated(){
            console.log("activated,我被激活显示了");
        },
        deactivated(){
            console.log("deactivated,我又被隐藏了");
        }
    }
    new Vue({
        el: "#app",
        data:{
            flag:true
        },
        components:{
            one
        }
    });
</script>

代码分析

在上面的代码里同,因为one的组件是被<keep-alive>,即使我使用了v-if想去销毁它,这也不行,它不会被销毁

当一个组件如果被 keep-alive以后,它就不会销毁,但是又多了两个特殊的生命周期钩子函数

  1. activated当激活【显示】的时候触发
  2. deactivated当休眠【隐藏】的时候触发

4. vue1.0与2.0生命周期对比

image.png

5. 生命周期图

image.png

关于上面的那个$mount的过程,大家可以参考下面的代码

let vm = new Vue({
    data:{
        age:18
    },
    computed:{
        userName(){
            return  "帅哥";
        }
    },
    methods:{
        sayHello(){
            console.log("我是一个方法")
        }
    },
});
vm.$mount("#app");

十二、关于组件的属性传值注意事项

在组件内部如果要进行传值 ,我们推荐使用自定义属性传值 ,如下所示

<one userName="张三"></one>
<!--向组件内部传递了一个属性,userName,值为"张三" -->

<one age="18"></one>
<!-- 向组件内部传递了一个属性 age,值为"18",请注意是字符串的18 -->

<one :age="18"></one>
<!-- 这代表向组件内部传递一个属性age,值为18,这里是数据类型的18 -->

<one show-back="true"></one>
<!-- 这代表向件内部传递showBack,值为字符串的"true" -->


<one :show-back="true"></one>
<!-- 这传递的就是布尔类型的true -->

易错点

<one show-back="false"></one>
<!--内部如果想接收布尔类型,但是我们传递了字符串"false",在转换的时候Boolean("false")结果是true -->

自定义属性在传递数据的时候默认都是字符中,如果是其它 的数据类型,请使用动态绑定

<one :show-back="false"></one>
1
vue

评论区