第06课:基础功能演练——组件开发

第06课:基础功能演练——组件开发

组件化开发是前端所提倡的,当你掌握了基础内容之后,就可以对组件这一大块内容进行了解了,本文将向你介绍组件相关内容,并带你开发简单的组件。

组件的概念

组件(Component)是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。

所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象(除了一些根级特有的选项)并提供相同的生命周期钩子。

如何理解组件

简单理解,组件其实就是一个独立的 HTML,它的内部可能有各种结构、样式、逻辑,某些地方来说有些像 iframe,它都是在页面中引入之后展现另一个页面的内容,但实际上它与 iframe 又完全不同,iframe 是一个独立封闭的内容,而组件既是一个独立的内容,还是一个受引入页面控制的内容。

为什么要使用组件

举个简单的列子,最近我的项目中有一个日历模块,多个页面都要用这个日历,而每个页面的日历都存在一些差别,如果不使用组件,我要完成这个项目,做到各个页面的日历大体一致,而部分地方存在差异,我可能就需要写几套日历代码了。

而使用组件呢?一套代码,一个标签,然后分别在不同地方引用,根据不同的需求进行差异控制即可。

<calendar></calendar>

我可以通过给 calendar 传递值实现在本页面对日历的控制,让它满足我这个页面的某些单独需求。

有人会问,你 calendar 标签是什么鬼?前面有这么一句话,组件是自定义元素。calendar 就是我自定义的元素,它就是一个组件。所以在项目中,你会发现有各种五花八门的标签名,他们就是一个个组件。

如何创建一个组件

我们把创建一个组件称为注册组件,如果你把组件理解成为变量,那么注册组件你就可以理解为声明变量。我们通过 Vue.component 来注册一个全局组件。

Vue.component(tagName, options)

tagName 就是你组件的名称,也是你引入时候使用的自定义元素名称,如下面这个代码,我们自定义了一个名为 my-component 的组件。

Vue.component('my-component', {
  // 选项
})

注意:对于自定义标签的命名,Vue.js 不强制遵循 W3C 规则(小写,并且包含一个短杠),尽管这被认为是最佳实践。

当你注册好组件之后,你就可以使用你的自定义元素 my-component 在其它实例中使用了。但是有一点,注册组件需要在你创建实例之前进行,请看下面代码。

<div id="example">
  <my-component></my-component>
</div>
// 注册
Vue.component('my-component', {
  template: '<div>this is my demo</div>'
})

// 创建根实例
new Vue({
  el: '#example'
})

当代码执行之后,你的 HTML 代码将会被渲染为:

<div id="example">
  <div>this is my demo</div>
</div>

前面说把组件比作变量,组件也存在局部组件与全局组件,前面为大家展示的是全局组件,接下来为大家介绍局部组件。

当你的组件不是全局都需要的时候,你就可以将其注册为局部组件。通过某个实例或者其他组件进行注册,你所注册的组件的作用范围就只存在与这个实例或者组件的作用域,参与如下代码。

var Child = {
  template: '<div>this is my demo</div>'
}

new Vue({
  // ...
  components: {
    // <my-component> 将只在父组件模板中可用
    'my-component': Child
  }
})

关于组件使用的限制,大家可参见官方文档

之前说过组件也是独立的,所以它也可以嵌套其它组件,所以你也可以在组件中注册组件。

组件之间的嵌套使用

组件设计初衷就是要配合使用的,最常见的就是形成父子组件的关系:组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。然而,通过一个良好定义的接口来尽可能将父子组件解耦也是很重要的。这保证了每个组件的代码可以在相对隔离的环境中书写和理解,从而提高了其可维护性和复用性。

每个组件的作用域都是独立的,所以在组件嵌套使用的时候子组件不能直接使用父组件中的数据。我们要如何在 Vue.js 中实现组件之间进行数据传递呢?

答案是 Prop。

Prop 是 Vue.js 的一个属性,它的作用就是由父组件向子组件传递数据。

在 Vue.js 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。

image

前面说过推荐使用-命名法,为什么要使用-,而不是驼峰命名法?

HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase(驼峰式命名)的 prop 需要转换为相对应的 kebab-case(短横线分隔式命名)。

Prop的使用

在父组件中声明 prop,然后添加一个 message,参见下面代码。

Vue.component('child', {
  // 声明 props
  props: ['message'],
  // 就像 data 一样,prop 也可以在模板中使用
  // 同样也可以在 vm 实例中通过 this.message 来使用
  template: '<span>{{ message }}</span>'
})

然后直接传入值就可以在子组件中使用 message。

<child message="hello!"></child>

通过这样的方法我们可以将某些内容传递给子组件,但有的时候需要在父元素的某些数据发生变化的时候子组件中的数据也要自动变化,这个时候应该如何做呢?

在之前的课程中我们有介绍过 v-bind,在这里我们也可以通过 v-bind 将数据绑定到组件上,这样父组件中的参数发生变化的时候子组件中的数据就会自动变化。

//:是v-bind的缩写
<child :my-message="message"></child>

如果我要传递的是个对象怎么办呢?使用上面的方法直接传递一个对象 message 可以这样做吗?

不,我们不这样做。我们通过 v-bind 来完成对象的传递。

有人会问 v-bind 不就是上面写的吗?让我们来看看下面的代码你就清楚了。

父组件中有个对象 userInfo。

userInfo: {
  name: 'lzh',
  age: '24'
}

我们通过 v-bind 传递给子组件。

<my-demo v-bind="userInfo"></my-demo>

注意仔细看,前一个是:

v-bind:xxxx="XXXXXX"

后面这个是:

v-bind="XXXXXX"

两者是不同的,后者你可以理解为:

<my-demo :name="lzh" :age="24"></my-demo>

这里有一个坑,日常容易出现的,就是使用 :age 传递的时候,这时传递的24并不是数值,而是字符串。

而我们在传递参数的时候可以对数据类型进行定义,如下所示。

props: {
    age: Number //这样传入的就只能是数值
}

也可以设置多种格式:

props: {
    age: [String, Number] //这样可以传递字符串和数值
}

在设置数据类型的时候还可以同时设置默认值:

props: {
    age: {
        type: Number,
        default: 24 //这可以指定默认的值
    }
}

其中 type 可以是:

String
Number
Boolean
Function
Object
Array
Symbol

当父元素中的值通过 v-bind 绑定到子组件中之后,如果我们要使用这些数据参与逻辑处理应该怎么做呢?直接使用可以吗?答案是"no"。

如果你需要在子组件中对传递的数据进行其他处理,你需要在子组件的 data 中定义一个变量,然后将传递的值赋给你定义的变量,参与下面代码。

props: ['date'],
data: function () {
  return {
  nowDate: this.date 

  }
}

上述内容就是如何通过 prop 从父组件传值给子组件。

既然父元素可以通过 prop 给子组件传值,那么子组件如何与父组件进行通讯呢?

$on(eventName)+$emit(eventName) 实现通讯

Vue 的自定义事件:"是时候表演真正的技术了",使用 v-on 绑定自定义事件。

每个 Vue 实例都实现了事件接口,即:

  • 使用 $on(eventName) 监听事件;
  • 使用 $emit(eventName) 触发事件。

$on(eventName)+$emit(eventName)的用法

在父组件中使用 $on(eventName) 监听事件,然后在子组件中使用 $emit(eventName) 触发事件,这样就能实现子组件向父组件传值。

Vue 的事件系统与浏览器的 EventTarget API 有所不同。尽管它们的运行起来类似,但是 $on 和 $emit 并不是 addEventListener 和 dispatchEvent 的别名。

另外,父组件可以在使用子组件的地方直接用 v-on 来监听子组件触发的事件。

不能用 $on 侦听子组件释放的事件,而必须在模板里直接用 v-on 绑定,参见下面的例子。

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
  template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
      console.log('第'+this.total+'次点击')
    }
  }
})

整体是如何工作的呢?

一个组件 button-counter,它绑定了一个 click 事件 incrementCounter。当你点击子组件button-counter 的时候,方法 incrementCounter 执行 this.counter += 1。然后通过 $emit 触发事件 increment,即 this.$emit('increment')。而父组件的 increment 实际上是事件 incrementTotal。所以最终执行的就是父组件中的 incrementTotal,即 this.total += 1,并输出你的点击次数。

整个流程就是这样,现在你也可以尝试自己写一个 DEMO 试试。

小结:组件的基础内容已经讲完了,为什么要用组件,为什么都提倡组件化的开发?大概是因为组件化开发具有内聚性和低耦合性。

什么是内聚性?简单来说,我的功能都在这个组件中完成了。

什么是低耦合性?我的组件在这里,谁都可以使用,但是不管谁使用都不会影响到其他人的使用,也不会影响到你的其他内容。

高复用性、宜维护性、内聚性、低耦合性,具备这四个优势的 Vue.js 组件,如果你不会使用,如何算得上会 Vue.js呢?

上一篇
下一篇
目录