终于到了Vue最核心的组件化开发!

组件化:我们将一个页面拆分成很多个组件,每个组件都用于实现页面的一个功能块,而每一个组件又可以进行细分。这样会让代码更加方便组织和管理,而且扩展性也更强。

Vue组件化:提供了一种抽象,让我们可以开发出一个个独立可复用的Vue组件来构造应用。任何应用都会被抽象成一棵组件树。

配套可执行代码示例 => GitHub

组件的基本使用

组件的使用分成三个步骤:

  1. 调用Vue.extend()方法创建组件构造器
  2. 调用Vue.component()方法注册组件
  3. 在Vue实例的作用范围内使用组件

注:第一步和第二步在后面用起来.vue文件之后,都可以省略。

<div id="app">
  <!-- 3.使用组件-->
  <my-cpn></my-cpn>
</div>

<script>
  //1.创建组件构造器
  const cpnC = Vue.extend({
    template: `
      <div>
        <h2>标题</h2>
        <p>内容内容内容</p>
      </div>`
  })
  // 2.注册组件
  Vue.component('my-cpn',cpnC)
  const app = new Vue({
    el: '#app'
  })
</script>

全局组件和局部组件

全局组件:如上述调用Vue.component()方法注册的组件,可以在任意Vue实例下使用

局部组件:通过components属性挂载在某个Vue实例中,只能在该Vue实例下使用

const app = new Vue({
  el: '#app',
  // 注册局部组件
  components: {
    'my-cpn': cpnC
  }
})

父组件和子组件

<script>
  //创建子组件构造器
  const cpnC1 = Vue.extend({
    template: `
      <div>
        <h2>标题1</h2>
        <p>内容1内容1内容1</p>
      </div>`
  })
  //创建父组件构造器
  const cpnC2 = Vue.extend({
    template: `
      <div>
        <h2>标题2</h2>
        <p>内容2内容2内容2</p>
        <cpn1></cpn1>
      </div>`,
    //在cpnC2中注册子组件cpnC1
    components: {
      cpn1: cpnC1
    }
  })

  const app = new Vue({
    el: '#app',
    data: {
    },
    //在app中注册子组件cpnC2
    components: {
      cpn2: cpnC2
    }
  })
</script>

在上面这个例子中,cpnC2是cpnC1的父组件,app又是cpnC2的父组件。可以把Vue实例看成最顶层的root组件。

注册组件的语法糖

主要是省去了调用Vue.extend()的步骤,直接使用一个对象来代替

注册全局组件的语法糖

Vue.component('cpn1', {
  template: `
    <div>
      <h2>标题</h2>
    </div>`
})

注册局部组件的语法糖

const app = new Vue({
  el: '#app',
  components: {
    'cpn2': {
      template: `
        <div>
          <h2>标题</h2>
        </div>`
    }
  }
})

组件HTML模板的分离写法

Vue提供了两种方法来定义HTML模板的内容:

  • script标签
<script type="text/x-template" id="cpn">
  <div>
    <h2>标题</h2>
    <p>内容内容内容</p>
  </div>
</script>
  • template标签(常用)
<template id="cpn">
  <div>
    <h2>标题</h2>
    <p>内容内容内容</p>
  </div>
</template>

注册组件时,用#id获取HTML模板:

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

注:组件模板必须包含一个根元素,一般用 <div>

组件中的data属性

每个组件都有自己的数据data,也有自己的方法methods。

需要注意的是,组件的data属性必须是一个函数,这个函数返回一个对象,对象内部保存着数据。

<template id="cpn">
  <div>
    <h2>{{title}}</h2>
    <p>内容内容内容</p>
  </div>
</template>

<script>
  Vue.component('cpn',{
    template: '#cpn',
    data() {
      return {
        title: 'abc'
      }
    }
  })
</script>

为什么组件中的data必须是一个函数?

因为一个组件的各实例之间不应该共享同一个data,所以需要把data定义成函数,这样每次创建组件实例时,data都会返回一个新的对象,从而每个组件实例都有一个属于自己的data。

组件通信-父传子props属性

需求:整个页面的大组件从服务器请求到了很多数据,需要下面的小组件进行展示。这时并不会让子组件再次发送一个网络请求,而子组件是不能访问父组件或者Vue实例的数据的,因此需要让父组件将数据传递给子组件。

第一步:在定义子组件构造器时,使用props属性来声明需要从父组件接受的数据。props的值有两种方式:

  • 字符串数组:数组元素就是传递时的数据名。
  • 对象:对象属性可以限制传递时的数据类型(type,可以是自定义类型)、提供的默认值(default,当类型是对象或数组时,default必须是一个函数,并return设置的默认值)、是否要求必须传值(required)等。

第二步:在使用子组件时,通过v-bind将父组件的data绑定到子组件的属性上,然后绑定的属性就可以在子组件的模板中像data一样使用了。

<!--父组件模板-->
<div id="app">
  <!--v-bind将父组件的data绑定到子组件的属性上-->
  <cpn v-bind:cmovies="movies" :cmsg="msg"></cpn>
</div>

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

<script>
  const cpn = {
    template: '#cpn',
    //子组件使用props属性来声明需要从父组件接受的数据
    //props: ['cmovies', 'cmsg'],
    props: {
      //限制数据类型(可以是自定义类型)
      cmovies: Array,
      cmsg: {
        type: String,
        //提供默认值(当类型是对象或数组时,default必须是一个函数,return设置的默认值)
        default(){
          return 'Hello'
        },
        //要求必须传值
        required: true
      }
    }
  }
  
  const app = new Vue({
    el: '#app',
    data: {
      msg: 'Hi',
      movies: ['海王', '海贼王', '海尔兄弟']
    },
    components: {
      cpn
    }
  })
</script>

注:props中如果是驼峰标识,则在组件中 v-bind绑定时需要转换成 “-”连接的方式

组件通信-子传父$emit自定义事件

需求:子组件中发生事件,需要传数据给父组件,父组件再根据接收到的值去请求新一轮的数据。

第一步:在子组件定义的methods中,通过$emit触发自定义事件,可以传入参数。

第二步:然后在父组件中使用子组件时,用v-on监听子组件的自定义事件,默认接收传入的参数。

<!--父组件模板-->
<div id="app">
  <!--v-on监听子组件的自定义事件-->
  <cpn v-on:item-click="cpnClick"></cpn>
</div>

<!--子组件模板-->
<template id="cpn">
  <div>
    <button v-for="item in categories" @click="btnClick(item)">
      {{item.name}}
    </button>
  </div>
</template>

<script>
  //子组件
  const cpn = {
    template: '#cpn',
    data() {
      return {
        categories: [
          {id: 'aaa', name: '热门推荐'},
          {id: 'bbb', name: '手机数码'},
          {id: 'ccc', name: '家用家电'}
        ]
      }
    },
    methods: {
      btnClick(item){
        //在子组件中触发自定义事件
        this.$emit('item-click',item)
      }
    }
  }

  //父组件
  const app = new Vue({
    el: '#app',
    data: {
    },
    methods: {
      cpnClick(item){
        console.log('cpnClick', item)
      }
    },
    components: {
      cpn
    }
  })
</script>

注:在HTML模板的标签中,v-on监听无法识别驼峰标识,因此自定义事件需采用 “-”连接小写字母的方式。

修改props传入的数据 (可跳过)

props传入的数据只能用于展示,而不能在<input>标签中修改,否则会报错。如果有修改的需求,以下两种方法:

  • 设置data属性,返回由传入的props数据初始化的对象,然后在<input>标签中绑定data属性,但仅仅这样不能让父组件中的data随着变化。再把<input>的v-model拆成v-bind和v-on,在@input绑定的方法中触发自定义事件并传入修改值,在父组件中监听事件,这样就可以在事件绑定的方法中修改父组件的data。如对number1的处理。
  • 同样需要先设置data属性,返回由传入的props数据初始化的对象,然后在<input>标签中绑定data属性。再设置watch属性,监听data的改变,在处理函数中触发自定义事件并传入修改值,在父组件中监听事件,并在事件绑定的方法中修改父组件的data。如对number2的处理。
<!--父组件模板-->
<div id="app">
  <cpn :number1="num1" :number2="num2" @num1change="num1change" @num2change="num2change"></cpn>
</div>

<!--子组件模板-->
<template id="cpn">
  <div>
    <h2>props:{{number1}}</h2>
    <h2>data:{{dnumber1}}</h2>
    <input type="text" :value="dnumber1" @input="num1Input">
    <h2>props:{{number2}}</h2>
    <h2>data:{{dnumber2}}</h2>
    <input type="text" v-model="dnumber2">
  </div>
</template>

<script>
  //父组件
  const app = new Vue({
    el: '#app',
    data: {
      num1: 1,
      num2: 0
    },
    methods: {
      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)
          }
        },
        watch: {
          dnumber2(newValue, oldValue) {
            this.dnumber2 = newValue
            this.$emit('num2change', newValue)
          }
        }
      }
    }
  })
</script>

组件访问-父访问子

this.$children:获取所有子组件,返回值为数组类型

this.$refs(常用):根据后缀获取指定的子组件,返回值为对象类型,如果不跟后缀默认返回一个空对象

<div id="app">
  <!--在父组件模板为子组件设置ref后缀-->
  <cpn ref="aaa"></cpn>
</div>

<script>
  const app = new Vue({
    ...
    //在父组件中根据ref后缀获取指定的子组件
    console.log(this.$refs.aaa);
  })
</script>

组件访问-子访问父

this.$parent:获取父组件,最顶层组件的父组件是Vue实例,返回值为对象类型

this.$root:获取根组件Vue实例,返回值为对象类型

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐