看完此篇,掌握Vue组件的实现

85 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情

什么是组件化?
  • 人面对复杂问题的处理方式:

任何一个人处理信息的逻辑能力都是有限的。 所以,当面对一个非常复杂的问题时,我们不太可能一次性搞定一大堆内容。 但是,我们人有一种天生的能力,拆分成很多个可以处理的小问题,再将其放在整体当中,你会发现问题也会迎刃而解。

比如新房装修 改水电 地板砖 木工进厂 刷漆 买家具 家电 入住

  • 组件化也是类似的思想:

如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。 但如果,我们将一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么,之后整个页面的管理和维护就会变得非常容易了。

Vue的组件化思想

组件: 页面上的一部分。

可以把一个大的网页, 拆分成很多个页面组件进行维护,根据页面组件中的通用功能抽离功能组件。

image-20220420115621828

  • 组件化思想的应用

    有了组件化的思想,我们在之后的开发中就要充分的利用它。

    尽可能的将页面拆分成一个个小的、可复用的组件。

    这样让我们的代码更加方便组织和管理,并且扩展性也很强。

注册组件的基本步骤

组件的使用分成三个步骤

  1. 调用Vue.extend()方法创建组件构造器
  2. 调用Vue.component()方法注册组件
  3. 在Vue实例的作用范围内使用组件
 // 1 构造组件
 const copyRight = Vue.extend({
     data() {
         return {
             message: '我是组件的信息'
         }
     },
     template: `<div>
                     {{message}}
                     <button @click="changeText">改变</button>
                 </div>
                 `,
     methods: {
         changeText() {
             console.log(123);
             this.message = '更改后的信息'
         }
     },
 })
 // 2 注册组件 - 全局注册
 Vue.component('copy-right', copyRight)
 // 3 使用
 <div id="app">
     <copy-right></copy-right>
 </div>

注册组件步骤解析:

  • 这里的步骤都代表什么含义呢?

Vue.extend():

调用Vue.extend()创建的是一个组件的构造器。 创建一个组件。

通常在创建组件构造器时,传入template代表我们自定义组件的模板。重要的是,组件的内容必须要用一个根元素来进行包裹,且只能有一个根元素

 当多个根元素,会报以下错误:
 vue.js:634 [Vue warn]: Error compiling template:
 ​
 Component template should contain exactly one root element. If you are using v-if on multiple elements, use v-else-if to chain them instead.
 ​
 4  |                    <child-msg></child-msg>
 5  |                </div>
 6  |                <div>123</div>
 |      ^^^^^^^^^^^^^^
 7  |                
 |  ^^^^
 ​
 found in
 ​
 ---> <ParentCpn>
     <Root>

Vue.component():

调用Vue.compoment()是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。

所需要传递两个参数:1、注册组件的标签名。2、组件构造器。

组件必须挂载在某个Vue实例下,否则不会生效

 <!DOCTYPE html>
 <html>
     <head>
         <meta charset="utf-8">
         <title></title>
         <script src="js/v2.6.10/vue.js"></script>
     </head>
     <body>
         <div id="app">
             <h1>{{ message }}</h1>
             <!-- 3. 在vue实例中,用闭合标签,写上定义的组件 -->
             <copy-right></copy-right>
         </div>
         <copy-right></copy-right>
         <script>
             // 1. 创建组件
             const copyright = Vue.extend({
                 template: `
                 <div>
                     <div>Copyright © 2020.科技公司  All rights reserved</div>
                     <div>网站备案号 ICP京12324</div>
                 </div>
                 `
             });
             // 2. 注册组件 参数:1 定义到vue实例中的 组件名。 2 叫做组件变量名
             Vue.component('copy-right', copyright); // 全局注册
             let app = new Vue({
                 el: '#app',
                 data() {
                     return {
                         message: '不忘初心 牢记使命'
                     }
                 },
             })
         </script>
     </body>
 </html>
 ​

组件的复用

全局组件和局部组件

首先,我们上面写的组件就是全局组件,可以在不同的Vue实例中使用,下面来说一下局部组件的写法。

局部组件:在vue实例中,添加components(复数)属性,可写多个。

 let vm = new Vue({
     el: '#app',
     data() {
         return {
 ​
         }
     },
     // 局部注册,对象,对象的属性为标签,加引号
     components: {
         'copy-right': copyRight
     },
 ​
 });

父组件和子组件

  • 定义:组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系。

语法糖注册组件 v-on @ v-bind :

  • 目的:简写,省略extend,将组件定义成一个对象。
全局注册
 Vue.component('my-cpn', {
   template: `
       <div>
         <h2>我是标题</h2>
         <p>我是内容,哈哈哈哈哈</p>
         <p>我是内容,嘿嘿嘿嘿嘿</p>
       </div>
     `
 });
局部注册
 const app = new Vue({
   el: '#app',
   data: {
 ​
   },
   components: {
     'cpn2': {
       template:`
       <div>
         <h2>我是标题</h2>
         <p>我是内容,哈哈哈哈哈</p>
         <p>我是内容,嘿嘿嘿嘿嘿</p>
       </div>
       `
     }
   }
 });

组件模板的分离

刚才,我们通过语法糖简化了Vue组件的注册过程,另外还有一个地方的写法比较麻烦,就是template模块写法。

如果我们能将其中的HTML分离出来写,然后挂载到对应的组件上,必然结构会变得非常清晰。

Vue提供两种方案来定义HTML模板内容:

使用<script>标签 type="text/x-template"

 <script type="text/x-template" id="myCom">
         <div>
             <h2>hello everone!</h2>
  </div>
 </script>

使用<template>标签

 <template id="myCom">
  <div>
      <h2>hello everone!</h2>
  </div>
 </template>

两者都要赋予id='xxx'的属性。

注册:

 Vue.component('cpn', {
  template: '#cpn'
 });

组件可以访问Vue实例数据么?

实验:

 <!DOCTYPE html>
 <html>
     <head>
         <meta charset="utf-8">
         <title></title>
         <script src="js/v2.6.10/vue.js"></script>
     </head>
     <body>
         <div id="app">
             <h1>app里的:{{ message }}</h1>
             <copy-right></copy-right>
         </div>
         <script>
             const copyright = Vue.extend({
                 template: `
                 <div> {{ message }} </div>
                 `
             });
             Vue.component('copy-right', copyright); // 全局注册
 ​
             let app = new Vue({
                 el: '#app',
                 data() {
                     return {
                         message: '不忘初心 牢记使命'
                     }
                 },
             })
         </script>
     </body>
 </html>
 ​

结论:我们经过测试,组件中不能直接访问Vue实例中的data。

Vue组件拥有属于自己的HTML模板,也有属于自己的数据data,即应该有自己保存数据的地方。

组件数据的存放

组件自己的数据存放在哪里?

  • 组件对象也有一个data属性(也可以有methods等属性)
  • 只是这个data属性必须是一个函数,Vue实例data可以是对象
  • 而且这个函数返回一个对象,对象内部保存着数据

组件通信

通信,即组件将自己的数据(data)进行传递.

在前面将父子组件的时候,我们提到子组件是不能引用父组件或者Vue实例的数据的。

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

比如在一个页面中,我们从服务器请求到了很多的数据。

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

通过props向子组件传递,通过事件向父组件发送消息。

下面我们直接将Vue实例当做父组件,并且其中包含子组件来简化代码。在真实开发中,Vue实例和子组件的通信与父组件和子组件的通信过程是一样的。

父子组件通信
父传子:props用法
  • 在组件中,使用选项props来声明需要从父级接收到的数据。

    props的值有两种方式

    • 方式一:字符串数组,数组中的字符串就是传递时的名称。
    • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值。

    先看一个简单的props传递案例:

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

     props: {
       cmovies: {
         type: Array,
         default() {
           return []
         },
       },
       cmessage: {
         type: String,
         default: '12345',
       }
     }
    

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

    String Number Boolean Array Object Date Function Symbol : ES6 中新添加的数据类型 作用: 给对象 唯一值 为了避免第三方框架的同名属性被覆盖

 <!DOCTYPE html>
 <html>
     <head>
         <meta charset="utf-8" />
         <title></title>
         <!-- <script src="https://unpkg.com/vue@next"></script> -->
         <script src="js/v2.6.10/vue.js" type="text/javascript" charset="utf-8"></script>
 ​
     </head>
     <body>
         <div id="app">
             <div>{{msg}}</div>
             <my-component :aaa="msg" url="http://www.baidu.com" webname="百度" :userlist="userList" :article="form"
                 :status=status>
             </my-component>
         </div>
 ​
         <template id="childComponent">
             <div>
                 <h1>{{ aaa }}</h1>
                 <a :href="url">{{webname}}</a>
                 <ul>
                     <li v-for="(item,index) in userlist">
                         {{ item }}
                     </li>
                 </ul>
 ​
                 <h4 v-for="(item,key) in article">
                     {{ item }}
                 </h4>
                 {{status}}
                 <div v-show="status">
                     显示状态切换
                 </div>
 ​
             </div>
         </template>
 ​
         <script>
             //  父亲传值给儿子
             let vm = new Vue({
                 el: '#app',
                 data() {
                     return {
                         msg: '我是父组件中的消息',
                         userList: [
                             "panzhoudan", "历代美国"
                         ],
                         form: {
                             title: '标题',
                             content: '今天我很开心,因为我要吃大餐,喝大酒,抽大烟',
                             create_time: '2022-02-30 15:00:33'
                         },
                         status: true
                     }
                 },
                 // 局部注册,对象,对象的属性为标签,加引号
                 components: {
                     'my-component': {
                         template: '#childComponent',
                         // 第一种,用props数组形式
                         // props: ['aaa', 'url', 'webname'] // 组件中的props 数组字符串接收
                         props: {
                             aaa: {
                                 type: String,
                                 default: 'abc'
                             },
                             url: {
                                 type: String,
                                 default: 'http://www.sina.com'
                             },
                             webname: {
                                 type: String,
                                 default: '新浪'
                             },
                             userlist: {
                                 type: Array,
                                 default: function() {
                                     return []
                                 }
                             },
                             article: {
                                 type: Object
                             },
                             status: {
                                 type: Boolean
                             }
                         },
 ​
                     }
                 },
 ​
             });
         </script>
     </body>
 </html>
 ​
子传父:自定义事件 v-on
  • props用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。我们应该如何处理呢?这个时候,我们需要使用自定义事件向父组件传递参数来完成。
  • 我们之前学习的v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。

自定义事件流程:

在子组件中,通过$emit()来触发事件。

在父组件中,通过v-on来监听子组件。

举例:click事件

  1. 子组件:模板template中,建立一个click事件,@click="post(text),将要传递的数据作为参数。

  2. 子组件:建立post方法,用this.$emit(),发射自定义指令名和发射的参数

     post(arg) {
         this.$emit('post-text', arg)
     }
    
  3. 在父!组件的模板HTML中,子组件标签上,添加自定义指令及方法

    注意@post-text="recData" recData 不要加参数或()

     <message-cpn @post-text="recData">
     </message-cpn>
    
  4. 在父组件中,写recData方法,来接收数据

     methods: {
         recData(text) {
             console.log(text);
         }
     },
    
 传递字符串
 <!DOCTYPE html>
 <html>
     <head>
         <meta charset="utf-8">
         <title></title>
         <script src="js/v2.6.10/vue.js"></script>
     </head>
     <body>
         <div id="app">
             <msg @post-text="rec"></msg>
         </div>
         <template id="msgTemplate">
             <div>
                 <button @click="post(text)">{{text}}</button>
             </div>
         </template>
         <script>
             let msg = {
                 data() {
                     return {
                         text: '五一节日快乐!'
                     }
                 },
                 methods: {
                     post(text) {
                         // $emit 用于子组件向父组件传递数据的方法。
                         // 两个参数: 1、向父组件提供的一个自定义指令名。 2、子组件要发送出去的数据。
                         this.$emit('post-text', text)
                     }
                 },
                 template: msgTemplate
             }
             let app = new Vue({
                 el: '#app',
                 components: {
                     msg
                 },
                 methods: {
                     rec(value) {
                         alert(value)
                     }
                 },
             })
         </script>
     </body>
 </html>
 ​
 ​
 ​
 ​
 传递对象
 <!DOCTYPE html>
 <html>
     <head>
         <meta charset="utf-8">
         <title></title>
         <script src="js/v2.6.10/vue.js"></script>
     </head>
     <body>
         <div id="app">
             <nav-list @item-click="goUrl"></nav-list>
         </div>
         
         <template id="navListTemplate">
             <div>
                 <button v-for="(item,index) in navList" @click="post(item)" >{{item.name}}</button>
             </div>
         </template>
         <script>
             let navList = {
                 data() {
                     return {
                         navList: [
                             {id:1,name:"java"},
                             {id:2,name:"html"},
                             {id:3,name:"css"},
                             {id:4,name:"js"}
                         ]
                     }
                 },
                 methods: {
                     post(item){
                         this.$emit('item-click',item)
                     }
                 },
                 template: navListTemplate
             }
             let app = new Vue({
                 el: '#app',
                 components: {
                     "nav-list":navList
                 },
                 methods: {
                     goUrl(item){
                         console.log(item);
                     }
                 },
             })
         </script>
     </body>
 </html>

访问组件DOM

  • $parent 在子组件中访问父级DOM 不常用
  • $children 在父组件访问子级DOM 1个子组件 肯定[0] 如果多个子组件 [0,1,2] 他的顺序是不一定的。需要数组进行一个遍历,才能应用
  • $root 访问根DOM
  • $refs 直接定位需要访问的组件