Vue2组件化开发

本文详细介绍了Vue2的组件化开发,包括组件注册、全局与局部组件、父子组件通信、数据传递、事件处理、插槽使用等核心概念,通过实例展示了如何创建、使用和管理组件,以及如何在组件间进行数据交互,帮助开发者深入理解Vue的组件化开发模式。
摘要由CSDN通过智能技术生成

Vue2组件化开发

一、组件化开发的基本使用

①组件注册的基本步骤:
  • 创建组件构造器
vue.exetend()
  • 注册组件
Vue.componen()
  • 使用组件
<div id="app">
    <!-- 3.使用组件 -->
    <!-- 组件必须放到vue的实例里才有效 -->
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
  <my-cpn></my-cpn>
  </div>
  <script src="../vue.js"></script>
  <script>
    // 1.创建组件构造器对象
    const cpnC = Vue.extend({
      template: ` <div>
      <h2>我是一个组件</h2>
      </div>`
    })
    // 2.注册组件
    // 两个参数 一个是注册组件标签名 另一个是组件构造器
    Vue.component('my-cpn',cpnC)

    const app = new Vue({
      el: '#app',
      data: {
        massage:'你好啊'
      }
    })
  </script>
②全局组件和局部组件:

全局组件:意味着可以在多个vue实例下面使用

<!-- 全局组件的使用 -->
  <div id="app">
   <cpn></cpn>
  </div>
  <div id="app2">
    <cpn></cpn>
  </div>
  <script src="../vue.js"></script>
  <script>
    const cpnC = Vue.extend({
      template: ` <div>
      <h2>我是一个组件</h2>
      </div>`
    })
    // 注册全局组件,意味着可以在多个vue实例下面使用
    Vue.component('cpn',cpnC)

    const app = new Vue({
      el: '#app',
    })
    const app2 = new Vue({
      el: '#app2',
    })
  </script>

局部组件: 在一个vue实例里注册的组件是局部组件

 <!-- 全局组件的使用 -->
  <div id="app">
      <!-- 在这里就不能使用<newcpn></newcpn> -->
  </div>
  <div id="app2">
    <newcpn></newcpn>
  </div>
  <script src="../vue.js"></script>
  <script>
    const cpnC = Vue.extend({
      template: ` <div>
      <h2>我是一个组件</h2>
      </div>`
    })
   
    const app = new Vue({
      el: '#app',
    })
    const app2 = new Vue({
      el: '#app2',
      // 在一个vue实例里注册的组件是局部组件
      components: {
        // newcpn 注册组件的标签名
        newcpn: cpnC
      }
    })
  </script>
③*父组件和子组件:
  <div id="app">
  <cpn2></cpn2>
  <!-- 这里不能使用cpn1组件 因为cpn1既没有在全局注册,又没有在vue实例里注册 -->
  </div>
  <script src="../vue.js"></script>
  <script>
    // 1.创建第一个组件(子组件)
    const cpnC1 = Vue.extend({
      template: `
      <div>
        <h2>这是第一个组件</h2>
      </div>
    `
    })

    // 2.创建第二个组件(父组件)
    const cpnC2 = Vue.extend({
      template: `
      <div>
        <h2>这是第二个组件</h2>
        <cpn1></cpn1>
      </div>
    `,
    // 在创建第二个组件时 注册第一个组件并使用
    components: {
      cpn1:cpnC1
    }
    })

    // 所以可以将vue实例当成最顶层的一个组件 root组件
    const app = new Vue({
      el: '#app',
      components: {
        // 在vue实例中注册cpn2组件
        cpn2:cpnC2
      }
    })
  </script>
④注册组件的语法糖:
<div id="app">
   <cpn1></cpn1>
   <cpn2></cpn2>
  </div>
  <script src="../vue.js"></script>
  <script>
    // 全局组件注册的语法糖
    Vue.component('cpn1',{
      template: `
      <div>
      <h2>我是一个全局组件</h2>
      </div>
      `
    })

    // 局部组件的语法糖
    const app = new Vue({
      el: '#app',
      components: {
        'cpn2': {
          template: `
           <div>
          <h2>我是一个局部组件</h2>
          </div>
          `
        }
      }
    })
  </script>
⑤组件模板抽离方法.:

第一种写法 用script标签 注意类型必须是text/x-template

<div id="app">
  <cpn></cpn>
  </div>
  <!-- 第一种写法 用script标签 注意类型必须是text/x-template-->
  <script type="text/x-template" id="cpn">
    <div>
      <h2>一个组件</h2>
    </div>
  </script>

  <script src="../vue.js"></script>
  <script>
    Vue.component('cpn',{
        // 这里直接对应id就行
      template:'#cpn'
    })
    const app = new Vue({
      el: '#app',
    })
  </script>

第二种写法 template标签(推荐) :

  <div id="app">
  <cpn></cpn>
  </div>
  <!-- 第二种写法  template标签(推荐)-->
  <template id="cpn">
    <div>
      <h2>一个组件</h2>
    </div>
  </template>
  <script src="../vue.js"></script>
  <script>
    Vue.component('cpn',{
      // 这里直接对应id就行
      template:'#cpn'
    })
    const app = new Vue({
      el: '#app',
    })
  </script>

组件是一个单独功能模块的封装
这个模块有属于自己的HTML模板,也应该有属性自己的数据data。

所以组件不可以访问Vue实例里的数据!即使能访问,如果将所有数据都放在vue实例中会显得非常臃肿!

vue组件应该有自己保存数据的地方

⑥*组件的data存放:

组件自己的数据存放在哪里呢?
组件对象也有一个data属性只是这个data属性必须是一一个!函数而且这个函数返回一一个对象,对象内部保存着数据

使用对象保存data可能会出现多个组件共享一个数据,会造成麻烦,而使用函数保存data避免了这个问题

<div id="app">
  <cpn></cpn>
  </div>
  <template id="cpn">
    <div>
      <h2>{{massage}}</h2>
    </div>
  </template>
  <script src="../vue.js"></script>
  <script>
    Vue.component('cpn',{
      template:'#cpn',
      // 组件内部可以有data属性,但是data不能像vue实例一样,data不能是对象
      // data只能写成一个函数
      data(){
        return {
          massage:'一个组件'
        }
      }
    })
    const app = new Vue({
      el: '#app',
    })
  </script>
⑦简单计数器的组件化写法:
<div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <!-- 多个组件不共享一个data对象 -->
  </div>
  <template id="cpn">
    <div>
      <h2>当前计数:{{counter}}</h2>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  </template>
  <script src="../vue.js"></script>
  <script>
    // 1.注册组件
    Vue.component('cpn',{
      template: '#cpn',
      data(){
        return {
          counter:0
        }
      },
      methods: {
        increment() {
          this.counter++
        },
        decrement() {
          this.counter--
        }
      }
    })

    const app = new Vue({
      el: '#app',
      data: {
        massage:'你好啊'
      }
    })
  </script>

二、父子组件的通信:

子组件不能直接引用父组件或者Vue实例里的数据,但是,在开发中,往往一些数据确实需要从上层传递到下层:比如在一个页面中,我们从服务器请求到了很多的数据。其中一部分数据,并非是我们整个页面的大组件来展示的,而是需要下面的子组件进行展示。
这个时候,并不会让子组件再次发送一个网络请求 ,而是直接让大组件(父组件)将数据传递给小组件(子组件)。

父子组件通信方法:
1.通过props向子组件传递数据

2.通过事件向父组件发送信息

在这里插入图片描述

在开发中,Vue实例和子组件间的通信和父子组件间的通信过程是一样的,所以可以将Vue实例看做一个父组件。

①父传子——props:

props的值有两种方式:
1 字符串数组:数组中的字符串就是传递时的名称。
2对象:对象可以设置传递时的类型,也可以设置默认值等。

 <!-- 通过对象传递(主要) -->

<div id="app">
    <!-- 注意这里要用v-bind 不然会将"movies"当做字符串赋值-->
   <cpn :cmovies="movies" :cmassage="massage"></cpn>
  </div>

  <template id="cpn">
    <div>
      <ul>
        <li v-for="item in cmovies">{{item}}</li>
      </ul>
      <h2>{{cmassage}}</h2>
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    // 父传子 props
    const cpn = {
      template:'#cpn',
      // props对象写法:
      // 这种写法的好处 1.是可以做到类型限制 2.可以提供一些默认值
      props: {
        cmovies:{
          type: Array,
        // 类型是对象或者数组时,默认值必须是一个函数
          default() {
            return []
          }
        },
        cmassage:{
          type:String,
          // 默认值 在没有传入数据的时候会显示默认值
          default:'你好啊',
          // 是否必传
          required: true
        }
      }
    }
    const app = new Vue({
      el: '#app',
      data: {
        massage:'你好啊',
        movies:['肖生克的救赎','楚门的世界','时空恋旅人']
      },
      components: {
        // 属性的增强写法
        cpn
      }
    })
  </script>
//props对象写法还可以这样写
props: {
        cmovies:Array
      }
 <!-- 通过字符串数组传递 -->
 props: ['cmovies','cmassage'] 
③子传父——自定义事件:

自定义事件的流程:

  • 在子组件中;通过$emit()来触发事件。
  • 在父组件中,通过v-on来监听子组件事件。
 <div id="app">
      <!-- 父组件监听事件 -->
      <!-- 这里的item-click是自定义事件 没有传参时不会传入事件对象 会把this.$emit('item-click',item)的item传过去 -->
      <cpn @item-click="cpnClick"></cpn>
  </div>
    
  <template id="cpn">
    <div>
      <button v-for="item in categories" @click="btnClick(item)">
        {{item.name}}</button>
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    // 子组件
    const cpn = {
      template: '#cpn',
      data() {
        return{
          categories: [
            {id:'AAA',name: 'title1'},
            {id:'BBB',name: 'title2'},
            {id:'CCC',name: 'title3'},
            {id:'DDD',name: 'title4'},
          ]
        }
      },
      methods: {
        btnClick(item){
          // 子组件发射自定义事件
          // (事件名 想要传给父组件的参数 )
          this.$emit('item-click',item)
        }
      }
    
    }
    // 父组件
    const app = new Vue({
      el: '#app',
      data: {
      },
      // 注册子组件
      components: {
        cpn
      },
      methods: {
        cpnClick(item) {
           console.log('cpnClick',item)
        }
      }
        
    })
  </script>
④子传父—计数器案例:

在子组件中实现计数器,并把结果传给父组件

<!-- 父组件 -->
  <div id="app">
   <child-cpn @increment="changeTotal" @decrement="changeTotal">
   </child-cpn>
   <h2>总数是:{{total}}</h2>
  </div>
  
  <template id="child-cpn">
    <div>
      <button @click="increment">+</button>
      <button @click="decrement">-</button>
    </div>
  </template>
  <script src="../vue.js"></script>
  <script>
      
// 父组件
    const app = new Vue({
      el: '#app',
      data: {
        massage:'你好啊',
        total:0
      },
      methods: {
        changeTotal(counter) {
          this.total = counter;
        }
      },
      components: {
        // 注册子组件
        // 在子组件的方法中实现计数器
        'child-cpn': {
          template:'#child-cpn',
          data() {
            return{
              counter:0
            }
          },
          methods: {
            increment() {
              this.counter++;
                // 通过发射事件将counter传到父组件
              this.$emit('increment', this.counter)
            },
            decrement() {
              this.counter--;
              this.$emit('decrement', this.counter)
            }
          }
        }
      }
    })
  </script>
⑤父子组件通信结合双向绑定案例:

要求:①子组件中input里默认数据来源于父组件中的num;

​ ②子组件中input里输入数据时,父组件num也发生改变;

思路:

​ 首先实现①:

<div id="app">
    <!-- step1:父组件将num值传到子组件props的number中 -->
    <!-- step2:子组件里,data相当于保存了一份props的数据,当data数据改变时不会影响props,然后input与data双向绑定 -->
    <!-- 总结就是 props给了输入框初始值, 而输入框值和data双向绑定,不干扰props -->
    <!-- 相当于input里的数据和子组件的data双向绑定,而子组件的data来源于父组件 -->
    <cpn :number1="num1"
         :number2="num2"/>
  </div>

  <template id="cpn">
    <div>
      <h2>props:{{number1}}</h2>
      <h2>data: {{dnumber1}}</h2>
      <!-- <input type="text" v-model="number1"> -->
      <!-- 这样写会报错,这里不能这样写 不能直接修改props里的数据,要修改只能用data里的 -->
      <input type="text" v-model="dnumber1">
      <!-- v-model只能绑定自己的data 不能绑定父类 子类-->

      <h2>props:{{number2}}</h2>
      <h2>data: {{dnumber2}}</h2>
      <input type="text" v-model="dnumber2">
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        num1: 1,
        num2: 0
      },
      components: {
        cpn: {
          template: '#cpn',
          props: {
            number1: Number,
            number2: Number
          },
          data() {
            return {
              dnumber1:this.number1,
              dnumber2:this.number2
            }
          }
        }
      }
    })
  </script>

在这里插入图片描述

然后实现②:组件中input里输入数据时,父组件num也发生改变;

在这里插入图片描述

 <div id="app">
   
    <!-- step1.子组件先将父组件传过来的props的值复制到data中,然后将data显示到input中 -->
    <!-- step2: 输入框中的数据又会改变子组件的data中的dnumber-->
    <!-- step3:  将事件和dnumber发送给父组件,使父组件中的num随dnumber改变-->
    <!-- 总结就是:props给了 input初始值,input后面输入的值又会改变data 从而通过发射事件改变num-->
    <cpn :number1="num1"
         :number2="num2"
         @num1change="num1change"
         @num2change="num2change"/>
  </div>

  <template id="cpn">
    <div>
      <h2>props:{{number1}}</h2>
      <h2>data: {{dnumber1}}</h2>
      <!-- v-model只能绑定自己的data 不能绑定父类 子类-->
      <!-- 要想绑定父类只能这样写 -->
      <input type="text" :value="dnumber1" @input="num1Input">
        
      <h2>props:{{number2}}</h2>
      <h2>data: {{dnumber2}}</h2>
      <input type="text" :value="dnumber2" @input="num2Input">
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        num1: 1,
        num2: 0
      },
      methods: {
        // 默认传过来的value是string类型
        num1change(value) {
          this.num1 = parseInt(value);
        },
        num2change(value) {
          this.num2 = parseInt(value);
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          props: {
            number1: Number,
            number2: Number
          },
          data() {
            return {
              dnumber1:this.number1,
              dnumber2:this.number2
            }
          },
          methods: {
            num1Input(event) {
              this.dnumber1 = event.target.value;
              this.$emit('num1change', this.dnumber1)
            },
            num2Input(event) {
              this.dnumber2 = event.target.value;
              this.$emit('num2change', this.dnumber2)
            }
          }
        }
      }
    })
  </script>

在这里插入图片描述

⑥父访问子—$children:
 <!-- 但是开发过程中一般不用这个方法访问子组件对象 -->
  <div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
    <button @click="btnClick">按钮</button>
  </div>

  <template id="cpn">
    <div>
      我是子组件
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊'
      },
      methods: {
        btnClick() {
          // 这里打印出来是一个对象数组
          console.log(this.$children);
          // 所以可以遍历
          for(let c of this.$children){
            console.log(c.name);
            c.showMessage();
          }
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return{
              name: '我是子组件的name'
            }
          },
          methods: {
            showMessage() {
              console.log('showMessage');
            }
          }
        }
      }
    })
  </script>
⑦07父访问子—$refs:
<div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <cpn ref="aaa"></cpn>
    <button @click="btnClick">按钮</button>
  </div>

  <template id="cpn">
    <div>
      我是子组件
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊'
      },
      methods: {
        btnClick() {
          // this.$refs默认为一个空对象
          console.log(this.$refs);
          // 但是给子组件加上ref属性之后 就会将属性的名字作为对象名展示
          console.log(this.$refs.aaa);
        }
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return{
              name: '我是子组件的name'
            }
          },
          methods: {
            showMessage() {
              console.log('showMessage');
            }
          }
        }
      }
    })
  </script>
⑧子访问父—parent-root:
 <!-- 但是开发过程中一般不用$parent访问父组件对象 -->
 
  <!-- 最大的组件Vue实例 -->
  <div id="app">
    <cpn></cpn>
  </div>
<!-- 第二个组件 -->
  <template id="cpn">
      <ccpn></ccpn>
  </template>
<!-- 最小的组件 -->
  <template id="ccpn">
    <div>
      <h3>我是子组件</h3>
      <button @click="btnClick">按钮</button>
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊'
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return{
              name:'我是cpn组件的name'
            }
          },
            components: {
              ccpn: {
                template: '#ccpn',
                methods: {
                  btnClick() {
                  // 1.访问我们的父组件 $parent
                  console.log(this.$parent)
                  console.log(this.$parent.name)

                  // 2.访问根组件:$root
                  console.log(this.$root)
                  // 打印出的是Vue实例
                }
              }  
            }
          }
        }
      }
    })
  </script>

三、组件化高级使用

①slot插槽的基本使用:
 <!-- 
    1.插槽的基本使用 <slot></slot>
    2.插槽的默认值 <slot><button>默认值</button></slot>
    3.如果有多个值同时放入到组件进行替换,一起作为替换元素
   -->
  <div id="app">
    <cpn><button>按钮</button></cpn>
    <cpn></cpn>
    <cpn></cpn>
    <cpn></cpn>
  </div>
  <template id="cpn">
    <div>
      <h2>我是一个组件</h2>
      <slot></slot>
      <!-- <slot><button>默认值</button></slot> -->
    </div>
  </template>
  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        
      },
      components: {
        cpn: {
          template:'#cpn'
        }
      }
    })
  </script>
②具名插槽的使用:
 <div id="app">
    <!-- 这里只能替换没有名字的插槽 -->
    <cpn><p>标题</p></cpn>
    <!-- 这样就可以替换有名字的插槽了 -->
    <cpn><span slot="center">替换中间</span></cpn>
  </div>
  <template id="cpn">
    <div>
     <slot name="left"><span>左边的插槽</span></slot>
     <slot name="center"><span>中间的插槽</span></slot>
     <slot name="right"><span>右边的插槽</span></slot>
     <slot></slot>
    </div>
  </template>
  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        
      },
      components: {
        cpn: {
          template:'#cpn'
        }
      }
    })
  </script>
③编译的作用域:

父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

<div id="app">
    <!-- 这里用的是实例里面的isShow -->
    <cpn v-show="isShow"></cpn>
    <!-- 因为这是写在Vue实例里的,所以模板里面使用变量时都是在实例里面寻找 -->
  </div>

  <template id="cpn">
    <div>
      <h2>我是子组件</h2>
      <!-- 这是在组件的模板里  所以使用的是组件里的isShow -->
      <button v-show="isShow"></button>
    </div>
  </template>

  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        isShow:true
      },
      components: {
        cpn:{
          template: '#cpn',
          data() {
            return{
              isShow: false
            }
          }
        }
      }
    })
  </script>
④作用域插槽的使用:

这样是一个普通的插槽使用:

 <div id="app">
    <cpn></cpn>
  </div>
  <template id="cpn">
    <div>
     <slot>
      <ul>
        <li v-for="item in pLanguages">{{item}}</li>
      </ul>
     </slot>
    </div>

  </template>
  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return{
              pLanguages: ['JavaScript', 'Python', 'Swift', 'Go','C++']
            }
          }
        }
      }
    })
  </script>

怎样改变数据的展示形式呢?

<div id="app">

    <!-- 如何使用不同方式展示呢 -->
    <!-- 主要父组件获取到子组件里的pLanguage -->
    <cpn>
      <template v-slot="slot">
        <span v-for="item in slot.data">{{item}} - </span>
      </template>
    </cpn>

    <cpn>
      <template v-slot="slot">
        <span v-for="item in slot.date">{{item}} * </span>
      </template>
    </cpn>

  </div>
  <template id="cpn">
    <div>
      <!-- 这里相当于把PLanguage传给data了 传给data就可以在父组件里使用 -->
     <slot :data="pLanguages">
      <ul>
        <li v-for="item in pLanguages">{{item}}</li>
      </ul>
     </slot>
    </div>

  </template>
  <script src="../vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        
      },
      components: {
        cpn: {
          template: '#cpn',
          data() {
            return{
              pLanguages: ['JavaScript', 'Python', 'Swift', 'Go','C++']
            }
          }
        }
      }
    })
  </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值