Vue组件

1,158 阅读5分钟

1.组件系统

  • 前端本身就是从后端分离出来 -> 量大【 项目任务重 】 -> 团队开发 -> 项目合【 难点 】 -> 如果分离出去任务是一个独立的整体,那么引用的时候就不会出现太大的问题 -> 组件【 独立的整体 】
  • 组件其实是一个html、css、js等的一个聚合体
  • Vue定义组件:Vue.extend() -> 帮助Vue扩展了组件的概念
    • Vue.extend() 得到的是一个构造函数VueComponent( options )
    const Hello = Vue.extend({ // 组件选项
     //template选项就是组件的模板 - 虚拟DOM结构
    template: `
      <div>
        <button @click = "aa"> 点击 </button>
        <p> Vue Hello Component </p>
      </div>
    `,
    methods: {
      aa () {
        alert('aa')
      }
    }
    })
    
    • VueComponent这个构造函数我们不进行new实例化,我们希望组件是以标签化的形式展示
    <Hello/> -->  <div></div>
    <Banner></Banner>
    
    • options即我们之前在new Vue( options )
  • Vue组件的结构
    • 树形结构
    • 根组件: 使用 new Vue() 来充当, 用来表示
  • 组件的使用形式
    • 标签化: 组件使用类似标签的形式
    • 组件既可以是单标签,也可以双标签(用单标签有时会出错,双标签更好)
  • 组件命名形式
    • 大驼峰形式
    • swiper-item(用-连接时用小写)

2.组件注册

  • Vue.extend(option)

  • 全局注册(常用)

    const Hello = Vue.extend({ // 组件选项
       //template选项就是组件的模板 - 虚拟DOM结构
      template: `
        <div>
          <button @click = "aa"> 点击 </button>
          <p> Vue Hello Component </p>
        </div>
      `,
      methods: {
        aa () {
          alert('aa')
        }
      }
    })
    Vue.component('Hello', Hello )//'Hello'即设定标签化的标签名<Hello></Hello>或<Hello/>
    //根据大驼峰形式命名,也可设为'VueHello',但标签名要改为<vue-hello></vue-hello>或<vue-hello/>
    
  • 局部注册

      const Hello = Vue.extend({
          template: '<div> 组件选项 </div>'
      })
      new Vue({
          el: '#app',
          data: {},
          methods: {
        
          },
          components: {
          // Hello: 组件的options
          Hello
          }
    })
    
  • 组件注册简写

    //全局注册
    Vue.component('Hello',{
        template: '<div> 组件注册简写 </div>'
    })
    new Vue({
        el: '#app',
        data: {},
        //局部注册
        components: {
            'Demo': {
                template: '<div> 局部注册简写 </div>'
            }
        },
    })
    

3.组件使用规则

  • 直接父子级管理标签问题

    • html有一些直接父子级管理标签: ul>li / table>tr>td

    • 当我们使用组件来代替子级元素时,组件会在父级外渲染

    • 解决问题: 使用is属性来解决 , is属性可以将一个组件绑定给一个元素

      <body>
        <div id="app">
          <table>
            <tr>
              <td>1</td>
              <td>2</td>
              <td>3</td>
            </tr>
            <!-- <Hello/>   直接用组件便签引入组件会在父级外渲染 -->
            <tr is = "Hello"></tr>
          </table>
        </div>
      </body>
      <script>
        Vue.component('Hello',{
          template: `
            <tr>
              <td> 4 </td>  
              <td> 5 </td>  
              <td> 6 </td>  
            </tr>
          `
        })
      
        new Vue({
          el: '#app',
          data: {},
          methods: {
            
          },
        })
      </script>
      

4.动态缓存

  • 动态组件

    • 可以变化的组件,用component来表示

    • 应用场景:后台管理系统

      <body>
        <div id="app">
          <select name="" id="" @change = "change">
            <option value="HelloA"> A </option>
            <option value="HelloB"> B </option>
            <option value="HelloC"> C </option>
          </select>
          <!-- 动态组件 -->
          <component :is = "type"></component>
        </div>
      </body>
      <script>
        Vue.component('HelloA',{
          template: '<div> A的内容 </div>'
        })
        Vue.component('HelloB',{
          template: '<div> B的内容 </div>'
        })
        Vue.component('HelloC',{
          template: '<div> C的内容 </div>'
        })
      
        new Vue({
          el: '#app',
          data: {
            type: 'HelloA'
          },
          methods: {
            change ( e ) {
              this.type = e.target.value 
            }
          },
        })
      </script>
      
  • 动态缓存

    • 当通过is属性在组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题

    • 通过keep-alive, 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

      <!-- 只需要在外面加一层keep-alive -->
      <keep-alive :include = "type">
          <component :is = "type"></component>
      </keep-alive>
      <!-- include 和 exclude 属性允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示(使用正则和数组需指令v-bind) -->
      <keep-alive include="a,b"></keep-alive>
      <keep-alive :include="/a|b/"></keep-alive>
      <keep-alive :include="['a', 'b']"></keep-alive>
      
    • 相关属性

      • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
      • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
      • max - 数字。最多可以缓存多少组件实例。

5.组件选项

  • template选项 - 组件的模板

    • template中只能有一个根元素

    • 组件中的template选项可通过标签放入body部分

      <body>
        <div id="app">
          <Hello></Hello>
          <template>
            <p> 你好 </p>
          </template>
        </div>
         <template id="hello">
             <!-- 唯一根元素 -->
            <div>
              <p> template标签在实例范围外使用 </p>
            </div>
         </template>
      </body>
      <script>
        /* 
          * template标签在实例范围外使用
            * 缺点: template标签将来会在渲染结构中出现,不会消失
          * template标签在实例范围内使用
            * 可以被解析,将来不会渲染到页面中   
        */
        Vue.component('Hello',{
          template: '#hello'
        })
        new Vue({
          el: '#app',
          data: {},
          methods: {
            
          },
        })
      </script>
      
  • data选项

    • 组件的data选项是个函数,因为组件是一个独立的整体,我们希望它的数据也是独立的,使用函数会有独立作用域

    • 因为data选项要经过数据劫持处理,data的返回值必须是一个对象

      data () {
          return {
              money: 10000
          }
      }
      
  • 其他选项如methods,computed,watch,directives,filters用法与根组件相同

6.组件嵌套

  • 组件中嵌套组件,使用形式如下

    <body>
      <div id="app">
        <!-- 父组件 -->
        <Father/>
      </div>
        
      <template id="father">
        <div>
          <h3> father组件 </h3>
          <!-- 子组件 -->
          <Son/>
        </div>
      </template>
        
      <template id="son">
        <div>
          <h5> Son组件 </h5>
        </div>
      </template>
    </body>
    <script>
    // 全局注册
    Vue.component('Father',{
        template: '#father'
      })
      Vue.component('Son',{
        template: '#son'
      })
      new Vue({
        el: '#app',
        data: {},
        // 局部注册
        components: {
           Father: {
             template: '#father',
             components: {
               Son: {
                 template: '#son'
               }
             }
           }
        },
      })
    </script>
    

7.组件通信

7.1 父-->子组件通信
  • 在父组件模板中,使用单项数据绑定,将父组件数据赋值给子组件的自定义属性

  • 子组件只可以使用接收到的数据,不可以更改 -> 单项数据流

  • 子组件通过props属性来接收绑定在自己身上的自定义属性

    • 这个属性在子组件模板中相当于全局变量,可以直接使用
    • props属性接收的数据再生命周期的初始阶段获取不到,故无法在mounted钩子函数中操作接收的数据
    <body>
      <div id="app">
        <Father/>
      </div>
      <!-- 父组件 -- start -->
        <template id="father">
          <div>
            <h3> 父组件 </h3>
            <!-- 在父组件模板中,使用单项数据绑定,将父组件数据赋值给子组件的自定义属性 -->
            <Son :money = "money"/>
          </div>
        </template>
      <!-- 父组件 -- end -->
        
      <!-- 子组件 --start -->
        <template id="son">
          <div>
            <h5> 子组件 </h5>
            <p> 我老爸给了我: {{ money }} </p>
          </div>
        </template>
      <!-- 子组件 --end -->
    </body>
    <script>
    /* 子组件只可以使用接收到的数据,不可以更改  -> 单项数据流*/
      Vue.component('Father',{
        template: '#father',
        data () {
          return {
            money: 2000
          }
        }
      })
      Vue.component('Son',{
        template: '#son',
        /* 子组件通过props属性来接收绑定在自己身上的自定义属性 */
        props: ['money'] // 这个属性在子组件模板中相当于全局变量,可以直接使用
      })
      new Vue({
        el: '#app',
        data: {},
        methods: {
        },
      })
    </script>
    
7.2 子-->父组件通信
  • 在父组件模板中,使用自定义事件绑定父组件的方法

  • 在子组件中使用this.$emit()触发自定义事件,并将子组件的数据作为参数传递给父组件

    <body>
      <div id="app">
        <Father/>
      </div>
      <template id="father">
        <div>
          <h3> father - 组件 </h3>
          <p> 我的小金库有: {{ gk }} </p>
          <hr>
          <!-- 在父组件模板中,使用自定义事件绑定父组件的方法 - get是自定义事件,名称随意 -->
          <Son @get = "changeGk"/>
        </div>
      </template>
    
      <template id="son">
        <div>
          <h5> son - 组件 </h5>
          <button @click = "give"> 给老爸红包 </button>
        </div>
      </template>
    
    </body>
    <script>
    
      Vue.component('Father',{
        template: '#father',
        data () {
          return {
            gk: 0
          }
        },
        methods: {
          changeGk ( val ) {
            this.gk = val
          }
        }
      })
     
      Vue.component('Son',{
        template: '#son',
        data () {
          return {
            hongbao: 8888
          }
        },
        methods: {
          give () {
            // 在这里调用自定义事件 get 
            // this.$emit('get',参数1,参数2,参数3 )
            this.$emit('get', this.hongbao )
          }
        }
      })
      new Vue({
        el: '#app',
        data: {},
        methods: {
          
        },
      })
    </script>
    
7.3 非父子级通信
  • 兄弟组件(同一个父级组件)通信

    • 通过子父级通信原理,在子组件a中使用this.$emit()触发父级自定义事件

    • 给子组件b的ref属性一个名字如son,父组件通过this.$refs.son可直接操作子组件b

      <body>
        <div id="app">
          <Father/>
        </div>
        <template id="father">
          <div>
            <h3> 父组件 </h3>
            <hr>
            <!-- 给子组件的ref设定标识名 -->
            <Son ref = "son"></Son>
            <hr>
            <Girl @kick = "kick"/>
          </div>
        </template>
      
        <template id="son">
          <div>
            <h4> Son组件 </h4>
            <img v-if = "f" src="xxx" alt="">
          </div>
        </template>
      
        <template id="girl">
          <div>
            <h4> Girl组件 </h4>
            <button @click = "hit"></button>
          </div>
        </template>
      
      </body>
      <script>
        Vue.component('Father',{
          template: '#father',
          methods: {
            kick () {
              // 这个方法中执行son组件的changeF
              this.$refs.son.changeF()
            }
          }
        })
      
        Vue.component('Son',{
          template: '#son',
          data () {
            return {
              f: false 
            }
          },
          methods: {
            changeF () {
              this.f = !this.f
            }
          }
        })
      
        Vue.component('Girl',{
          template: '#girl',
          methods: {
            hit () {
              this.$emit('kick')
            }
          }
        })
      
        new Vue({
          el: '#app',
          data: {},
          methods: {
            
          },
        })
      </script>
      
  • 无关联组件通信

    • 新建Vue实例如bus

    • 在组件a中的mounted钩子函数中定义bus的自定义事件,可通过该事件对组件a进行数据修改

    • 在组件b中触发bus的自定义事件,达到通信的效果

      const bus = new Vue() // 事件总线
      Vue.component('Son',{
          template: '#son',
          data () {
              return {
                  f: false 
              }
          },
          mounted () { // 表示组件已经挂载结束,也就是表示这个组件已经渲染到了页面
              // console.log('mounted')
              bus.$on('cry',() => {// cry事件被触发时会修改son组件的数据
                  this.f = !this.f
              })
          }
      })
      Vue.component('Girl',{
          template: '#girl',
          methods: {
              hit () {//调用hit函数时即触发bus的cry事件
                  bus.$emit('cry')
              }
          }
      })
      

8.生命周期

  • Vue生命周期可以分为三个阶段,分别为: 初始化、更新阶段、销毁阶段

  • Vue生命周期钩子函数一共有11个,重点有8个

  • 是组件就有生命周期

8.1初始阶段
  • 初始阶段有四个钩子函数

    • beforeCreate 实例被创建之前,为实例事件和生命周期做准备
    • created 组件创建结束,初始化数据【 给data的数据做数据劫持,实现深入响应式原理 】
    • beforeMount vdom虚拟dom生成,
    • mounted 真实dom生成 (常用)
  • 若实例或组件没有el/template选项,需要在实例创建结束之后通过 $mount 手动进行挂载

    new Vue({
        data: {
          info: '相亲'
        },
        methods: {},
        beforeCreate () {},
        created () {},
        beforeMount () {},
        mounted () {}).$mount('#app')
    
  • 初始阶段自动执行,且只执行一次

  • created,beforeMount,mounted函数里都可以修改数据,一般都在mounted函数中操作

8.2 更新阶段
  • 更新阶段有两个钩子函数

    • beforeUpdate 监听 data 中数据是否改变,如果改变那么就执行更新阶段
    • updated 更新结束,(重新生成VDOM,并通过diff算法比对新旧VDOM,获得patch补丁对象,然后渲染成真实DOM )
8.3 销毁阶段
  • 销毁阶段有两个钩子函数
    • beforeDestroy 销毁之前
    • destroyed 销毁组件无法销毁开启在window上的事件,如window.onscroll,setInterval等
8.4 其他三个钩子函数
  • 缓存的状态
    • activated keep-alive 组件激活时调用。
    • deactivated keep-alive 组件停用时调用。
  • 错误捕获 errorCaptured
    • 当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。
    • 你可以在此钩子中修改组件的状态。因此在模板或渲染函数中设置其它内容的短路条件非常重要,它可以防止当一个错误被捕获时该组件进入一个无限的渲染循环。

基础决定未来,一步一个脚印