Vue独立组件开发: prop event slot

954 阅读6分钟

本文正在参加「金石计划」

本文是介绍独立组件开发的系列文章的第一篇。

Vuejs无疑是当下最火热的前端框架,而它最精髓的,正是它的组件与组件化。写一个 Vue 项目,就是在写一个个的组件,掌握了 Vue 组件的各种开发模式与技巧,再复杂的业务场景也可轻松拿捏。

组件的分类

Vue 组件分成三类:

页面组件

由 vue-router 产生的每个页面,它本质上也是一个组件(.vue),它承载当前页面的 HTML 结构,同时会有数据获取、数据整理等业务逻辑。

页面组件整个文件相对较大,但一般不会有 props 选项和 自定义事件,因为它作为路由的渲染,不会被复用,因此也不会对外提供接口。

在我们平时的项目开发中,写的大部分代码都是这类的组件。多人开发时,每人维护自己的页面,很少有交集。这类组件相对是最好写的,因为主要是还原设计稿,完成需求,不需要太多模块和架构设计上的考虑。

独立组件

它不包含业务,是独立、具体功能的组件,比如下拉框日期选择器等。

这类组件作为项目的基础控件,会被大量使用,因此组件的 API 进行过高强度的抽象,可以通过不同配置实现不同的功能。

大部分的互联网都有自己的组件使用规范或组件库,但是要开发和维护一套像 element uiant design 这样的组件库,投入的人力和精力还是很重的,所以出于成本考虑,很多项目都会使用已有的开源组件库。

独立组件的开发难度要远高于页面组件,因为它的侧重点是 API 的设计、兼容性、性能、以及复杂的功能。

这类组件对 JavaScript 的编程能力有比较高的要求,也包含非常多的技巧与经验。比如在不依赖 Vuex等第三方库(因为独立组件,无法依赖其它库)的情况下,各组件间的通信等等问题。

业务组件

它与独立组件的区别是,业务组件只在当前项目中会用到,不具有通用性,而且会包含一些业务,比如数据请求;而独立组件不含业务,在任何项目中都可以使用,功能单一,比如一个具有数据校验功能的输入框。

业务组件更像是介于第一类和第二类之间,在开发上也与独立组件类似,但依赖于项目,你可以使用项目中引入的第三方库,比如 Vuexaxioslodash 等,所以它的开发难度相对独立组件要容易点,但也有必要考虑组件的可维护性和复用性。

因此,独立组件的开发是最能体现一个前端开发者的Vue水平的,本文也将重点介绍独立组件的各种开发模式和技巧。

Vue组件的三个 API:prop、event、slot

一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue 组件的 API

如果你开发的是一个独立组件,那一定要事先设计好这三部分,因为组件一旦发布,后面再修改 API 就很困难了,使用者都不希望一个库的接口经常变换。

如果你阅读别人写的组件,也可以从这三个部分展开,它们可以帮助你快速了解一个组件的所有功能。

属性 prop

prop 定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。

写独立组件时,props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值,这点在组件开发中很重要,然而很多人却忽视,直接使用 props 的数组用法,这样的组件往往是不严谨的

比如我们封装一个按钮组件 <v-button>

<template>
  <button :class="'v-button-size' + size" :disabled="disabled"></button>
</template>
<script>
// 判断参数是否是其中之一
function oneOf(value, validList) {
  for (let i = 0; i < validList.length; i++) {
    if (value === validList[i]) {
      return true
    }
  }
  return false
}

export default {
  props: {
    size: {
      validator(value) {
        return oneOf(value, ['small', 'large', 'default'])
      },
      default: 'default'
    },
    disabled: {
      type: Boolean,
      default: false
    }
  }
}
</script>

使用组件:

<v-button size="large"></v-button>
<v-button disabled></v-button>

组件定义了两个属性:尺寸 size 和是否禁用 disabled

size 使用 validator 进行了值的自定义验证,也就是说,从父级传入的 size,它的值必须是指定的 small、large、default 中的一个,默认值是 default,如果传入这三个以外的值,都会抛出一条警告。

组件里定义的 props,都是单向数据流,只能通过父级修改,组件自己不能修改 props 的值,只能修改定义在 data 里的数据,非要修改,也是通过后面介绍的自定义事件通知父级,由父级来修改。

在使用组件时,也可以传入一些标准的 html 属性,比如 idclass

<v-button id="btn1" class="btn-submit"></v-button>

这样的 html 属性,在组件内的 <button> 元素上会继承,并不需要在 props 里再定义一遍。

这个特性是默认支持的,如果不期望开启,在组件选项里配置inheritAttrs: false就可以禁用了。

插槽 slot

如果要给上面的按钮组件 <v-button> 添加一些文字内容,就要用到组件的第二个 API:插槽 slot,它可以分发组件的内容:

<template>
  <button :class="'i-button-size' + size" :disabled="disabled">
    <slot></slot>
  </button>
</template>

这里的 <slot> 节点就是指定的一个插槽的位置,这样在组件内部就可以扩展内容了:

<v-button>按钮 1</v-button>
<v-button>
  <strong>按钮 2</strong>
</v-button>

当需要多个插槽时,会用到具名 slot,比如我们再增加一个 slot,用于设置另一个图标组件:

<template>
  <button :class="'i-button-size' + size" :disabled="disabled">
    <slot name="icon"></slot>
    <slot></slot>
  </button>
</template>
<v-button>
  <v-icon slot="icon" type="checkmark"></v-icon>
  按钮 1
</v-button>

这样,父级内定义的内容就会出现在组件对应的 slot 里,没有写名字的,就是默认的 slot

在组件的 <slot> 里也可以写一些默认的内容,这样在父级没有写任何 slot 时,它就会出现:

<slot>提交</slot>

自定义事件 event

现在给组件 <v-button> 加一个点击事件,目前有两种写法,我们先看自定义事件 event:

<template>
  <button @click="handleClick">
    <slot></slot>
  </button>
</template>
<script>
  export default {
    methods: {
      handleClick (event) {
        this.$emit('on-click', event);
      }
    }
  }
</script>

通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:

<v-button @on-click="handleClick"></v-button>

上面的 click 事件,是在组件内部的 <button> 元素上声明的,这里还有另一种方法,直接在父级声明,但为了区分原生事件和自定义事件,要用到事件修饰符 .native,所以上面的示例也可以这样写:

<v-button @click.native="handleClick"></v-button>

如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click(原生事件是指在html标签上定义的click事件,在组件上定义的事件为自定义事件),但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。

总结

本文复习了 Vue 组件的核心知识点prop、slot、evnet,虽然没有完全覆盖 Vue 的 API,但对于组件开发来说已经足够了。

prop定义了组件有哪些功能,开发组件首先需要明确prop,推荐使用对象的形式来写prop

slot分发组件的内容,让用户可以自定义展示内容,极大的扩展了组件的使用范围。

event组件内的数据改变,只能通过父级改变,保证数据的单向流动的。

在下一篇文章,我们将介绍独立组件之间的通信,看看它们在不依赖于第三库的情况下是如何通信的。