Vue-组件化开发

137 阅读9分钟

image.png

1. vue组件化思想

组件化提供了一种抽象,让我们可以开发一个个独立的可复用的小组件来构建我们的应用。

任何的应用都会被抽象成一颗组件树。

img

组件化思想的应用:

  1. 尽可能将页面拆分为一个个小的,可以复用的组件
  2. 这样让我们的代码更加方便组织和管理,并且扩展性也更加强大

2. 组件化基本使用详情

2.1 注册组件的基本步骤

组件化的使用主要分为三个步骤:

  1. 创建组件构造器
  2. 注册组件
  3. 使用组件

在这里插入图片描述

下面我们按照以上的步骤来写一个简单的组件:


<body>
  <div id="app">
  //第三步:在Vue的实例中使用组件
    <my-cpn></my-cpn>
  </div>
</body>
<script src="../vue.js"></script>
<script>
 //第一步:创建组件构造器
  var cpn = Vue.extend({
    template: `<div>
    <h1>我是一个组件化的标题</h1>
    <p>我是组件化的一个p表亲</p>
  </div>`
  })
 //第二步:注册组件
  Vue.component('my-cpn', cpn)
  
  var vue = new Vue({
    el: '#app'
  })
</script>

2.2 全局组件与局部组件

上面的例子实际上是一个全局组件,什么是全局组件呢,就是在任意一个Vue实例都可以使用。

那么什么又是局部组件呢, 顾名思义,就是只在特定的Vue实例才能使用的。如何定义?很简单,只需要将其第二步注册操作写在Vue实例内部即可。

实际上在我们开发过程中一般都是全部一个Vue实例,并且使用最多的也是局部组件

局部组件的实例:

 var cpn = Vue.extend({
    template: `<div>
    <h1>我是一个组件化的标题</h1>
    <p>我是组件化的一个p表亲</p>
  </div>`
  })
  // Vue.component('my-cpn', cpn)

  var vue = new Vue({
    el: '#app',
    components:{
      mycpn:cpn
    }
  })

2.3 父组件与子组件

前面说到,在真实开发中,可能会出现组件中嵌套组件的可能。因此这样就引出来了父子组件的概念。

即在父组件中在注册另外的组件,然后直接在父组件的template中使用即可。

我们看一下代码:

  var cpn1 = Vue.extend({
    template: `<div>
    <h1>我是一个组件化的标题1</h1>
    <p>我是组件化的一个p表亲</p>
  </div>`
  })
  var cpn2 = Vue.extend({
    template: `<div>
    <h1>我是一个组件化的标题2</h1>
    <p>我是组件化的一个p表亲</p>
    <mycpn1></mycpn1>
    </div>`,
    components:{
      mycpn1:cpn1
    }
  })
  var vue = new Vue({
    el: '#app',
    components: {
      mycpn2: cpn2
    }
  })

以上代码就是父子组件的使用,即我们直接使用标签,就可以达到我们的预期。

但是我们只能直接在html代码中使用标签,因为此组件并没有在vue实例中注册,只在父组件中注册。

2.4 组件的分离写法

在上面我们定义组件时都会将我们的组件使用使用字符串的形式来表示出来,这样写的结果时是我们的代码看着杂乱无章。

因此我们就有了组件的分离写法。

在介绍组件的分离写法之前,我先介绍以下前面组件创建的语法糖的形式,替代了extend。

就是直接将extend内的对象参数放在注册操作的第二个参数中。这样写Vue底层其实还是还调用extend方法进行创建组件。

 Vue.component('mycpn', {
    template:`内容`
 })

接下来我介绍以下组件的分离写法:

1. 使用script标签:

组件内容:

  <script type="text/x-template" id="test1">
    <div>
      <h1>我是一个组件化的标题</h1>
      <p>我是组件化的一个p表亲</p>
    </div>
  </script>

注册:

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

2. 使用template标签:

组件内容:

  <template id="test1">
    <div>
      <h1>我是一个组件化的标题</h1>
      <p>我是组件化的一个p表亲</p>
    </div>
  </template>

注册:同上

3. 组件化的数据存放data

3.1 组件数据的存放

首先我先问一个问题,组件中的数据可以放在Vue实例中吗?

答案是不能,为什么,我在这块的理解是,组件时一个具有特定功能功能的模板。因此它一定有自己存放数据的地方,如果它放在顶层Vue的实例中,那无疑是失去了组件化开发的初衷。

话不多说,我们可以试试看:

 <div id="app">
    <mycom></mycom>
  </div>
  <template id="mypom">
    <h2>{{title}}</h2>
  </template>
</body>
<script src="../vue.js"></script>
<script>
   new Vue({
     el:'#app',
     data:{
       title:'哈哈'
     },
     components:{
       mycom:{
         template:'#mypom'
       }
     }
   })

结果并不能显示出来,因此我们得出结论,组件中的数据不可以放在Vue实例中。

实际上我们组件数据的存放时存在extend的参数对象中的data属性中的,Vue官方规定data属性必须是一个函数,且返回值必须是一个对象,在对象内部存放着需要的数据。

我们看一下代码:

 Vue.component('mycom',{
    template:'#mypom',
    data(){
      return {
        title:'我是标题'
      }
    }
  })
  //使用:
   <template id="mypom">
    <h2>{{title}}</h2>
  </template>

那么我们是否考虑过一个问题,组件的data属性为什么必须是一个data返回一个对象。接着看:

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

首先vue的组件大多是用来复用的,那么他每复用一次,就会使用我们的data属性,如果这时我们data是一个对象时,那么当我们复用组件时则会出现一个问题,一个组件中数据变化,那么所有的组件中的数据都会变化。因此vue的底层在data属性这里添加了这个设定。

当然在开发过程中也可能会遇到这个属性,这个可以将对象暴露出去再引入,实现所有组件数据的一致性。

我们可以看一下一个很简单的例子:

写一个计数器的组件

 Vue.component('mycom', {
    template: '#mypom',
    data() {
      return {
        message: 0
      }
    },
    methods: {
      add() {
        this.message++
      },
      decre() {
        this.message--
      },
    },
  })

使用:

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

这时我们会发现每个组件都是独立的,这时因为,这就归功于data属性是一个函数。因此他们不会共同使用一个对象内的数据。

但是如果我们有这种一个动,全部动的需求时,也很容易。

  var obj={
    message: 0
  }
  Vue.component('mycom', {
    template: '#mypom',
    data() {
      return obj
    },
    methods: {
      add() {
        this.message++
      },
      decre() {
        this.message--
      },
    },
  })

这就可以实现我们想要的效果。

4. 父子组件通信

既然我们知道了父子组件的概念,那么又有一个问题相应的出来了。想象一个场景,如果我们现在有一个网络请求的需求,但是我们的界面采用的时组件化的方式进行的,首先可以肯定的是我们的网络请求操作肯定是在最外层的组件进行的,但是如果这时我们的子组件需要父组件请求到的一些数据时,那么这时就有了。父子组件通信的概念,我们不可能在每个组件中都执行相应的网络请,这样固然是行不通的。

下图是组件通信的一个方式流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjcbyuTk-1617281327816)(C:\Users\LuAo\AppData\Roaming\Typora\typora-user-images\image-20210401145415915.png)]

4.1 父传子

父传子的一个使用场景就是我们上文说到的。

父传子主要是由一个props属性来进行的。

我们直接看一些代码:

 var cpm = {
    template: '#cpm',
    props: ['cmovies', 'cmessages'],

  }
  new Vue({
    el: '#app',
    data: {
      movies: ['电影1', '电影2', '电影3'],
      messages: '消息'
    },
    components: {
      mycon:cpm  
    }
  })

在js代码中我们通过props属性自定义了两个自己的参数。并且我们的父组件使用的是Vue实例。

再看使用:

  <div id="app">
    <mycon :cmovies='movies' :cmessages='messages'></mycon>
  </div>
  <template id="cpm">
     <div>
      <ul>
        <li v-for='item in cmovies'>{{item}}</li>
      </ul>
    </div>
  </template>

然后通过v-bind来将Vue实例(父组件)的值传给props属性的值。这时组件中的prop对应属性就有了对应的值。

手动分割线------------------------------------------------------------------------------------

除了在props中使用数组以外,我们还可以通过对象的方式来进行为其赋值。并且这样更加灵活,可以为其数据验证,以及设定默认值等等。这也是在开发中使用较多的一种情况。

如下实例:

props:{
      cmovies:{
        type:Array,
        default:['无值'],
        required:true
      },
      cmessages:{
        type:String,
        default:'哈哈',
      }
    }

4.2 子传父

子传父的使用场景其实也很多,我模拟一个场景,如果我们现在做一个商城的分类页面,但是我们的数据请求操作依然在最上层的父组件中,当用户点击某一个选项时,我们子组件需要向父组件传递自己的index以便于向父组件请求哪一条数据。

这里的实现其实时在子组件上使用 this.$emit('事件名',内容)向父组件发射一个类似于自定义事件的东西,然后父组件在通过v-on尽心接受,在使用方法处理即可:

代码实现如下:

  <template id="cpm">
    <div>
      <button v-for='item in categories' @click='btnclick(item)'>{{item.name}}</button>
    </div>
  </template>

组代码

var cpm = {
    template: '#cpm',
    data() {
      return {
        categories: [
          { id: "aaa", name: '热门' },
          { id: "bbb", name: '手机' },
          { id: "ccc", name: '电器' },
        ]
      }
    },
    methods: {
      btnclick(item){
        this.$emit('btnclick',item)
      }
    },
  }

以上代码就是我们向父组件发射事件.

 <div id="app">
    <mycon @btnclick='requestbuttonevent'></mycon>
  </div>

接着使用组件,在父组件的作用范围内使用该组件定义一个自定义事件。

new Vue({
    el: '#app',
    components: {
      mycon: cpm
    },
    methods: {
      requestbuttonevent(e){
        console.log('父组件methods',e);
      }
    },
  })

在父组件的实例中可以接受到我们子组件传来的数据,即代码中的e,接着我们可以继续进行我么接下来的请求等操作。

5.插槽

插槽的存在一般是在一个组件中我们可能有一些不同的小功能,这时我们就可以将这些小功能做成插槽来使用

5.1 普通使用

在template中定义一个插槽

  <template id="cpn">
    <div>
      <h2>标题2</h2>
      <slot></slot>
    </div>
  </template>

使用:

 <div id="app">
    <mycpn></mycpn>
    <mycpn>
      <button>插槽</button>
    </mycpn>
    <mycpn></mycpn>
  </div>

除此之外我们还可以为其设定一个默认值,然后如果直接引入插槽时则显示默认值,若不需要重写,则在使用时重写即可。

5.2 具名插槽的使用

何为具名插槽,就是给插槽赋一个name值,然后在在使用时可以识别我们的插槽,避免一些复用错误等等。

下面看一个案例,需求:我们现在有一个组件中有三个插槽并分别由默认值,我如何在使用时改变其中一个?

组件代码:

  <template id="cpn">
    <div>
      <slot name='left'><span></span></slot>
      <slot name='middle'><span></span></slot>
      <slot name='right'><span></span></slot>
    </div>
  </template>

将middle替换为button:

  <div id="app">
    <mycpn><button slot="middle">搜索</button></mycpn>
  </div>

5.3 作用域插槽

在了解作用域插槽之前我们必须明白一个点:

父模板的所有东西都会在父级作用域内编译,子模板的所有东西都会在子级作用域内编译

也就是说在正常情况下,父组件不能访问子组件的数据,因此当我们使用插槽时有一些需要父组件使用子组件数据的需求时,就有一些特定的操作。

使用场景:当插槽为我们处理好业务后,但是当我们向再次使用插槽但是想基于原数据对业务进行不同的操作时可以使用作用域插槽

概括为一句话就是:父组件重新处理插槽中业务,但是数据由子组件提供

我们研究以下代码:

  new Vue({
    el:'#app',
    components:{
      mycpn:{
        template:'#cpn',
        data() {
          return {
            language:['a','b','c','d','e']
          }
        },
      }
    }
  })

在Vue实例中定义组件mycpn,这时Vue实例相当于父组件。

<template id="cpn">
    <div>
      <slot :nba='language'>
        <ul>
          <li v-for='item in language'>{{item}}</li>
        </ul>
      </slot>
    </div>
  </template>

定义模板,并在slot中绑定一个自定义属性,属性的内容为我们想要暴漏出去的数据。

<div id="app">
    <mycpn></mycpn>
    <mycpn>
      <template slot-scope="slot">
        <span>{{slot.nba.join('-')}}</span>
      </template>
    </mycpn>
  </div>

使用slot-scope="slot"固定格式接受slot的属性值,然后直接使用slot.数据进行操作