Vue 组件化开发

组件化开发

基本概念

   在最开始的时候,已经大概的聊了聊Vue是单页面开发,用户老是在一个页面上进行操做,看到的不一样内容也是由不一样组件构成的。html

   经过用户的操做,Vue将会向用户展现某些组件,也会隐藏某些组件。vue

   一个Vue的项目就是一个Vue的实例对象。而用户看到的页面则是Vue.component的实例对象。es6

   对于一些复用性高的内容,咱们也能够将它封装成一个单独的组件,如导航栏、搜索框、版权信息等等。后端

   因此说组件是Vue的核心、可是本章节不会讨论Vue如何实现组件的显示、隐藏,而是聊一聊如何使用组件。app

   image-20201115114950426

认识组件

根组件

   被挂载管理的元素块就是一个根组件。在此根组件中能够嵌套多个子组件,根组件通常来讲一个就够了。异步

  

<body>
    <div id="app">

    </div>
    <script src="./vue.js"></script>
    <script>
        const app = new Vue({
            el:"#app",
        })
    </script>
</body>

子组件

   子组件便是被嵌套在根组件中的组件,子组件可复用,而且数据都是相互独立的。函数

   子组件能够拥有根组件所拥有的任意的属性,如data/methods/computed/watch/filter组件化

   须要注意一点:组件名字若是有多个单词构成,应当写成驼峰形式(我的推荐)。ui

   而且在模板中进行引用时,当以-进行分割。this

   image-20201115120054672

<body>

<div id="app">
    <!-- 使用全局组件 -->
    <cpn-header></cpn-header>
    <cpn-header></cpn-header>
</div>

<script src="./vue.js"></script>
<script>
    // 定义组件名字与模板
    Vue.component("cpnHeader", {
        template: "<div><span>这是一个头部组件</span></div>",
    })
    const app = new Vue({
        el: "#app",
    })
</script>
</body>

组件声明

全局子组件

   全局子组件会自动进行注册,任何Vue的实例都能进行使用。

   使用Vue.component("name",{})对其进行声明并注册。

<body>

<div id="app">
    <!-- 使用全局组件 -->
    <cpn-header></cpn-header>
    <cpn-header></cpn-header>
</div>

<script src="./vue.js"></script>
<script>
    // 定义组件名字与模板
    Vue.component("cpnHeader", {
        template: "<div><span>这是一个头部组件</span></div>",
    })
    const app = new Vue({
        el: "#app",
    })
</script>
</body>

局部子组件

   局部子组件在Vue实例下使用components进行注册。它仅供当前实例使用,以下所示:

<body>

<div id="app">
    <cpn></cpn>
    <cpn></cpn>
</div>

<script src="./vue.js"></script>
<script>
    // 定义组件
    const cpn = {
        template:"<div><mark>HELLO,WORLD</mark></div>",
    }

    const app = new Vue({
        el:"#app",
        components:{  // 进行注册
            cpn,  //es6语法
        }
    })
</script>
</body>

组件模板

根标签

   每个组件对象都应该具备template属性,它应当是一个HTML字符串。

   而且,要拥有一个根标签在下面,不然将会抛出警告信息:

   image-20201120200646941

   当发生这样的警告信息,你应该检查一下你的子组件模板,并给他套上根标签,以下所示:

// 定义组件
    const cpn = {
        template: `
        <div>  
            <div>
                HELLO,VUE
            </div>
            <div>
                HELLO,WORLD
            </div>
        </div>
        `,
    }

抽离写法

   若是在定义组件时在template属性中写HTML代码,是不太友好的,你能够将模板抽离出来。

  1. 使用script标签,并添加type="text/x-template"的属性
  2. 使用template标签

   以下所示,使用<template>标签配合id属性将其做为子组件模板:

<body>

<div id="app">
    <cpn></cpn>
    <cpn></cpn>
</div>

<template id="cpn">
    <div>
        <div>
            HELLO,VUE
        </div>
        <div>
            HELLO,WORLD
        </div>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    const cpn = {
        template: "#cpn",
    }

    const app = new Vue({
        el: "#app",
        components: { 
            cpn,
        }
    })
</script>
</body>

错误示范

   若是你书写了一个DOM可识别的标签,则是错误的操做,以下所示我使用了main标签来定义组件模板,很显然DOM认识它,就会本身先一步渲染它再交由Vue进行处理,这会致使咱们的组件模板会多一次渲染。

   image-20201115122413269

<body>

<div id="app">
    <cpn-header></cpn-header>
    <cpn-header></cpn-header>
</div>

<!--子组件模板-->
<main id="cpn-header-template">
    <div>
    	<span>这是一个头部组件</span>
    </div>
</main>

<script src="./vue.js"></script>
<script>
    var cpnHeader = {
        template: "#cpn-header-template",
    }
    const app = new Vue({
        el: "#app",
        components: {  // Vue实例内部进行注册
            cpnHeader,
        }
    })
</script>
</body>

子组件的data

   上面已经说过,子组件能够拥有data/methods/computed/watch/filter等对象。

   可是须要注意的是子组件的data必须是一个函数,且必须返回一个Object

   这是由于每一个子组件是相互独立的,若是data是一个对象,那么获取全部的子组件数据都会引用同一个Object

   因此子组件中的data必须是一个函数,由于函数调用时会从新申请内存,返回一个全新的Object

<body>

<div id="app">
    <cpn></cpn>
</div>

<template id="cpn">
    <div>
        <p>{{childrenMessage}}</p>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    const cpn = {
        template: "#cpn",
        data() {
            return {
                childrenMessage: "子组件数据",
            }
        }
    }

    const app = new Vue({
        el: "#app",
        components: {
            cpn,
        }
    })
</script>
</body>

组件通讯

通讯意义

   组件之间的通讯是十分有必要的,例如当Vue项目启动后,父组件获取到了一些数据,它将如何把这些数据分发到子组件上。

   再好比,子组件上产生了一个新数据,它将如何把该数据交由父组件?

props

   父组件向子组件传递数据时,则经过props进行传递。

   接收值在父组件模板中采用 - 分割命名,在props中采用驼峰式,在子组件模板中进行使用时采用与props中一样的命名方式

   这是由于 - 分割命名方式不是Js的合法变量名,若是props中采用 - 来进行命名,子组件模板在使用时将查询不到该变量名

   具体操做步骤以下所示:

   image-20201120204132757

<body>

<!--根组件模板-->
<div id="app">
    <father></father>
</div>

<!--子组件模板-->
<template id="son">
    <div>
<!--        ④ 子组件如今已经能够正常渲染出该值了,使用接收的变量名便可-->
        <p>{{childerRecv}}</p>
    </div>
</template>

<!--父组件模板-->
<template id="father">
    <div>
<!--② 子组件经过v-bind,将父组件中的值存储到props中,须要注意的是再模板中应该使用 - 进行多单词分割-->
        <son :childer-recv="fatherMessage"></son>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    const son = {
        template: "#son",
        // ③ 如今子组件中已经用childerRecv这个变量名接收到父组件中传递过来的值了,须要注意的是接收时使用驼峰式命名进行接收,不然模板中不会渲染
        props: ["childerRecv",]
    }

    const father = {
        template: "#father",
        components: {
            son,
        },
        data() {
            return {
                // ① 父组件的做用域内可以接收到该值了
                fatherMessage: {id: 1, name: "yunya", age: 18},
            }
        }
    }


    const app = new Vue({
        el: "#app",
        components: {
            father,
        }
    })
</script>
</body>

   上面这个是三层组件嵌套,可能看起来有点绕。我这里有个双层嵌套的,看起来比较简单:

<body>
<div id="app">
    <!-- 接收: - 分割命名 -->
    <cpn :recv-msg="sendMsg"></cpn>
</div>

<!-- 子组件模板 -->
<template id="cpn-template">
    <!-- 使用: 与props一样的命名方式 -->
    <div><span>接收到的信息:{{recvMsg}}</span></div>
</template>

<script src="./vue.js"></script>
<script>
    var cpn = {
        // props:使用驼峰式命名,为了子组件模板中可以使用而不产生报错
        props: ["recvMsg",],
        template: "#cpn-template",
        data: function () {
            return {}
        }
    }
    const app = new Vue({
        el: "#app",
        data: {
            sendMsg: {
                id: 1,
                name: "admin",
            }
        },
        components: {  // Vue实例内部进行注册
            cpn,
        }
    })
</script>
</body>

   画张图,让你更容易理解:

   image-20201120204754847

props数据验证

   在上述例子中,父组件从后端获取的数据传递到子组件时没由进行任何验证就直接渲染了,这可能致使子组件渲染错误。

   因此在子组件接收父组件数据时进行验证是十分必要的流程。

   咱们能够发现,上述例子的props接收是一个array,若是要使用验证,则接收要用Object,以下示例:

验证项目 描述
type 一个Array,容许的类型
required 一个Boolen,是否必须传递
default 任意类型
validator 一个Function,返回须要验证的数据字段
<body>

<div id="app">
    <cpn :recv-msg="sendMsg"></cpn>
</div>

<!-- 模板 -->
<template id="cpn-template">
    <div><span>接收到的信息:{{recvMsg}}</span></div>
</template>

<script src="./vue.js"></script>
<script>
    var cpn = {
        props: {  // props是一个Object
            recvMsg: {  // 参数验证是一个Object
                // 容许的类型
                type: [Object, Array],
                // 是不是必须传递
                required: true,
                // 若是没有传递的默认值
                default() {
                    return "默认值";
                },
                // 验证,当验证失败后,会在调试台显示错误
                validator(v) {
                    // v就是父组件传递过来的数据
                    return v.id;
                },
            },
        },
        template: "#cpn-template",
        data: function () {
            return {}
        }
    }
    const app = new Vue({
        el: "#app",
        data: {
            sendMsg: {
                // id: 1,
                name: "admin",
            }
        },
        components: {  // Vue实例内部进行注册
            cpn,
        }
    })
</script>
</body>

   因为父组件中传递的数据不具备id字段,因此控制台抛出异常,可是不会影响正常渲染:

   image-20201115155932001

$emit

   当子组件中发生某一个事件,咱们可使用父组件对其进行处理。

   使用$emit进行自定义事件,由父组件进行处理,示例以下:

   咱们使用子组件定义了一个加法的运算,可是结果倒是在父组件中显示,须要子组件将计算结果发送给父组件。

   若是自定义事件的单词有多个,则在Js中采用驼峰形式,在html中采用 - 分割形式

   image-20201115155119333

<body>

<div id="app">
<!--    父组件监听add事件,而且交由fatherAdd进行处理-->
    <cpn @add="fatherAdd"></cpn>
    结果:{{showResult}}
    <!-- 结果显示在父组件 可是计算确是在子组件 -->
</div>

<!-- 子组件模板 -->
<template id="cpn-template">
    <div>
        <input type="text" v-model.number="n1"> + <input type="text" v-model.number="n2">
        <button @click="sum">计算</button>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    var cpn = {
        template: "#cpn-template",
        data() {
            return {
                n1: 0,
                n2: 0,
            }
        },
        methods: {
            sum() {
                let sonResult = this.n1 + this.n2;
                // 自定义了一个add事件,由父组件进行监听。而且传递了一个值
                this.$emit("add", sonResult);
            }
        }
    }
    const app = new Vue({
        el: "#app",
        components: {  // Vue实例内部进行注册
            cpn,
        },
        data: {
            showResult: 0,
        },
        methods: {
            fatherAdd(v) {
                this.showResult = v;
            }
        }
    })
</script>
</body>

   仍是画一张图梳理一下流程,这其实都是固定的用法:

   image-20201120205801581

.sync

   若是子组件的数据来自于父组件,当子组件中的数据发生改变时咱们也想让父组件中的数据发生一样的改变。

   则可使用.sync修饰符(尽可能少用,会破坏单一性),以下所示:

   Vue的组件通讯.sync

<body>

<div id="app">
    <span>父组件的值:{{num}}</span>
    <cpn :son-num.sync="num"></cpn>
</div>

<!-- 子组件模板 -->
<template id="cpn-template">
    <div>
        <span>子组件的值:{{sonNum}}</span>
        <p><input type="text" @keyup="changeValue" v-model="newValue" placeholder="输入新的值"></p>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    
    var cpn = {
        props: ["sonNum",],
        template: "#cpn-template",
        data: function () {
            return {
                newValue: this.sonNum,
            }
        },
        methods: {
            changeValue() {
                this.$emit("update:sonNum", this.newValue)
            }
        }
    }
    
    const app = new Vue({
        el: "#app",
        data: {
            num: 100,
        },
        components: {  // Vue实例内部进行注册
            cpn,
        },
    })
</script>
</body>

   流程图以下:

   image-20201115162925120

组件访问

$children

   有的时候咱们想直接经过父组件拿到子组件这个对象,调用其下面的某一个方法,可使用$children属性完成操做。

   以下所示,父组件想调用子组件中的show()方法:

   一个父组件可能有多个子组件,因此该属性$children是一个Array

   Vue的组件访问$children

<body>

<div id="app">
    <button @click="btn">父组件调用子组件方法</button>
    <cpn></cpn>
</div>

<!-- 子组件模板 -->
<template id="cpn-template">
    <div>
        <span>{{msg}}</span>
    </div>
</template>

<script src="./vue.js"></script>
<script>

    var cpn = {
        template: "#cpn-template",
        data: function () {
            return {
                msg: "子组件show未调用",
            }
        },
        methods: {
            show() {
                this.msg = "子组件show已调用";
            }
        }
    }

    const app = new Vue({
        el: "#app",
        components: {  // Vue实例内部进行注册
            cpn,
        },
        methods: {
            btn() {
                // 取出第0个子组件,进行调用其下方法
                this.$children[0].show();
            }
        }
    })
</script>
</body>

$refs

   上述的访问方法并不经常使用,由于父组件很是依赖索引值来访问子组件。

   使用$refs来访问子组件就方便的多,咱们须要给子组件取一个名字,再用父组件进行调用,这个是很是经常使用的手段。

   Vue的组件访问$children

<body>

<div id="app">
    <button @click="btn">父组件调用子组件方法</button>
    <!-- 取名字 -->
    <cpn ref="nbcpn"></cpn>
</div>

<!-- 子组件模板 -->
<template id="cpn-template">
    <div>
        <span>{{msg}}</span>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    
    var cpn = {
        template: "#cpn-template",
        data: function () {
            return {
                msg: "子组件show未调用",
            }
        },
        methods: {
            show() {
                this.msg = "子组件show已调用";
            }
        }
    }
    
    const app = new Vue({
        el: "#app",
        components: {  // Vue实例内部进行注册
            cpn,
        },
        methods: {
            btn() {
                // 根据取的名字进行调用
                this.$refs.nbcpn.show();
            }
        }
    })
</script>
</body>

$parent

   若是在子组件中想拿到父组件的对象,使用$parent便可,若是存在多层嵌套,它只会拿本身上一层。

   一个子组件,在一块被挂载区域中只有一个父组件

   Vue的组件访问$parent

<body>

<div id="app">
    {{msg}}
    <cpn></cpn>
</div>

<!-- 子组件模板 -->
<template id="cpn-template">
    <div>
        <button @click="btn">子组件调用父组件方法</button>
    </div>
</template>

<script src="./vue.js"></script>
<script>
    
    var cpn = {
        template: "#cpn-template",
        data: function () {
            return {}
        },
        methods: {
            btn() {
                this.$parent.show();
            }
        }
    }
    
    const app = new Vue({
        el: "#app",
        data: {
            msg: "父组件show未调用",
        },
        components: {  // Vue实例内部进行注册
            cpn,
        },
        methods: {
            show() {
                // 取出本身的父组件,进行调用其下方法
                this.msg = "父组件show已调用";
            }
        }
    })
</script>
</body>

$root

   若是存在三级或以上嵌套,能够直接使用$root来访问根组件。与$parent使用相同,可是它是具体的一个对象,而并不是Array,因此这里再也不作演示。

组件的动态切换

   使用:is属性,可让咱们动态使用不一样的组件。

   以下,定义了一个input框和文本域两个组件,当咱们点击不一样按钮,它就会选择不一样的组件。

   Vue的动态改变组件

<body>

<div id="app">
    <div :is="choice"></div>
    <input type="radio" v-model="choice" value="inputCPN">文本框
    <input type="radio" v-model="choice" value="textareaCPN">文本域
</div>

<script src="./vue.js"></script>
<script>

    const inputCPN = {
        template: "<div><input type='text'/></div>",
    }

    const textareaCPN = {
        template: "<div><textarea></textarea></div>",
    }

    const app = new Vue({
        el: "#app",
        data: {
            choice: "inputCPN",
        },
        components: {
            inputCPN, textareaCPN,
        },
    })
</script>
</body>

组件的钩子函数

   一个Vue的项目就是由不一样的组件构成,不论是局部注册也好,全局注册也罢,Vue官方都给你提供了一些钩子函数供你调用,以下图所示:

   生命周期图.png

钩子函数 描述
beforeCreate 建立Vue实例以前调用
created 建立Vue实例成功后调用(能够在此处发送异步请求后端数据)
beforeMount 渲染DOM以前调用
mounted 渲染DOM以后调用
beforeUpdate 从新渲染以前调用(数据更新等操做时,控制DOM从新渲染)
updated 从新渲染完成以后调用
beforeDestroy 销毁以前调用
destroyed 销毁以后调用

   以下所示,定义这几个钩子函数:

   vue组件钩子函数

<div id="app">
    <p>{{msg}}</p>
</div>

<script src="./vue.js"></script>
<script>

    let app = new Vue({
        el: "#app",
        data:{
            msg:"HELLO,VUE",
        },
        beforeCreate() {
            console.log("建立Vue实例以前...");
        },
        created() {
            console.log("建立Vue实例成功...");
        },
        beforeMount() {
            console.log("准备渲染DOM...");
        },
        mounted() {
            console.log("渲染DOM完成...");
        },
        beforeUpdate() {
            console.log("准备从新渲染DOM...");
        },
        updated() {
            console.log("从新渲染DOM完成");
        },
        // 后两个暂时体会不到
        beforeDestroy() {
            console.log("准备销毁当前实例");
        },
        destroyed() {
            console.log("当前实例销毁完成...");
        }
    })
</script>
</body>
相关文章
相关标签/搜索