vue组件封装基础,万物皆可封装,哎,我就是玩~

·  阅读 477

一、组件分类

vue的组件大致可以分为两种:基础组件、业务组件

基础组件就只是最基础的组件,不涉及到任何的业务,比如element所提供的组件,在系统中,它能用作某种特定的功能,也是业务组件或者其他组件实现个性化需求的基础;

业务组件是为了实现某种特定业务存在的,业务是它设计和实现的主要考虑因素之一,它往往会依赖基础组件去实现;

不管是基础组件还是业务组件,都具有的共性就是可复用性,拓展性,如果不可复用,拓展,就要考虑设计的必要性了。

二、组件构成

个人认为设计一个组件要考虑三个方面的东西:

  • 模板

    组件是视图方面的,是必不可少的一部分,也就是说组件最终被渲染成什么样式是用户关心的一件事情,产品设计的要求,除了基本的视图是需要我们预先写好的之外,我们还需要提供可拓展的插槽slot,这部分的视图内容应该由使用者来决定。

  • 数据

    对于一个组件来说,数据的处理是较为核心的部分,由于vue是以数据驱动视图的,模板的最终形态是会受到数据影响,而组件之间的通信最先改变的也是数据。因此,能否处理好组件内部的数据是比较考验一个开发者的能力的。

  • 事件

    一般来说,业务组件在一定程度上是会依赖基础组件去实现的,而业务组件又会被应用到各个页面中去,为了更好的满足业务的实现,组件必须向使用者提供特定的事件用于彼此的沟通,这里的使用者并不是指用户本身,而是与它具有层级关系的父组件或者兄弟组件,它们沟通的就是内容就是数据,这一点很重要。从页面的表现上来说,这个过程体现为交互行为,从前端的角度来说,而业务的实现正是用户人为的去操作页面,从而触发组件里面的事件,激活组件之间的沟通,从而达到数据的改变,最终得到有价值的数据传递给后端,进行各种解构,分析,加工,存储。

三、一个简单的组件实现

1、基本的父子组件通信

一个组件会被别的组件使用,形成父子组件,一般来说,父子组件的通信是这样的:

  • 子组件通过props属性接受父组件的传来的数据
  • 父组件监听子组件通过$emit方法向上发射的事件来获取子组件传来的数据

但是,有的场景希望可以在父组件中以双向数据绑定的方式与子组件进行数据通信,因此vue提供了model选项,将可双向数据绑定的数据使用prop来定义,将向上传递该值的发射的自定义事件使用event来定义。

但是需要注意的一点就是:

  • prop定义的值需要同时在props中定义

当然组件之间的通信方式还有很多种,以后会专门写一篇文章来介绍(想看就先关注吧)。

下面我们来基于input元素来实现一个最简单的input组件,这个例子来自vue官方文档

<template>
  <label>
    {{ label }}
    <input
      type="input"
      :value="value"
      @input="$emit('input', $event.target.value)"
    >
  </label>
</template>

<script>

export default {
  model: {
    prop: 'value',
    event: 'input'
  },
  props: {
    label: {
      type: String,
      default: ''
    },
    value: {
      type: String,
      default: ''
    }
  },
};
</script>

<style></style>
复制代码

在上面的例子中,value这个值是双向数据绑定的,向上传递该值的发射的事件是input,那input事件的发射时机是什么呢?就是<input>元素支持的原生事件input,因此只需要监听input事件,即当用户进行输入操作就触发了向上发射事件input,并将value向上传递给父组件。而在父组件中任何场景下与子组件双向数据绑定的值有任何的改变都能同步给子组件。

2、组件中的事件

上面提到了“自定义事件”和“原生事件”,下面就来做一下区分,但是之前先介绍一下以下的内容

  • 原生事件

    原生事件涉及的内容比较多,我单独写了一篇文章去介绍,详细可以参考javascript事件流、事件处理程序、事件类型

  • 自定义事件

    事件是浏览器行为或者用户行为,响应某个事件的函数就是事件处理程序。比如用户点击行为,在代码中如果要响应这个事件去做一些业务逻辑就必须写一个事件处理程序(事件侦听器)。 原生事件中的HTML事件处理程序是怎样实现的呢? 某个元素支持的每种事件都可以使用一个与之对应事件处理程序同名的HTML特性来指定。这个特性的值是可以执行的javascript代码。一般来说,我们会把这个值定义成一个函数,在函数体里面去实现一些业务逻辑,这个函数就是我们自定义的事件。 在vue里面,事件处理程序正是采用HTML事件处理程序的实现方式,因此vue组件中的自定义事件,大家应该知道是怎么的了吧。

3、将原生组件绑定到组件中

有时候自己写了一个组件比如某个卡片,我希望可以点击卡片去实现一些业务逻辑,这个时候你会很自然的写下类似这样的代码:

<base-card v-on:click.native="onClick"></base-card>
复制代码

通过.native修饰符,这在大多数情况下是可以的,因为对某个组件进行事件监听,其实就是在一个组件的根元素上直接进行监听,而这个根原元素往往是div,对于大部分的HTML元素来说都是支持原生点击事件的。

但是,如果你监听div的input事件,这就无法响应了。因为div标签根本就不支持input事件,从这一点来说,如果你写的组件的根元素是不支持某一个原生事件的,你通过.native修饰符对组件进行原生事件的监听是没有效果的,如果你不知道原因,你就会十分的烦恼了,因为它不会产生任何报错。

要解决这个问题要具体分析场景,比如上面的例子:

input-label.vue

<template>
  <label>
    {{ label }}
    <input
      type="input"
      :value="value"
	  @input="$emit('input', $event.target.value)"
    >
  </label>
</template>

<script>

export default {
  props: {
    label: {
      type: String,
      default: ''
    },
    value: {
      type: String,
      default: ''
    }
  },
};
</script>

<style></style>


<input-label @focus.native="input"></input-label>
复制代码

这时候监听点击事件时无效的,因为label元素不支持focus事件,但是如果在子组件中针对某一具体的元素进行监听,并且对外发射自定义事件,这个时候在父组件中不使用.native修饰符是可以实现监听到子组件发射出来的自定义事件的,这是为什么呢?因为在页面中用户真正操作的地方是input输入框的区域,按照子组件与父组件的通信方式来说这是完全没有问题的。

<template>
  <label>
    {{ label }}
    <input
      type="input"
      :value="value"
      @focus="$emit('focus')"
    >
  </label>
</template>

<script>

export default {
  model: {
    prop: 'value',
    event: 'input'
  },
  props: {
    label: {
      type: String,
      default: ''
    },
    value: {
      type: String,
      default: ''
    }
  },
};
</script>

<input-label @focus="onFocus"></input-label>
复制代码

那么麻烦来了,如果我要在父组件使用子组件时监听了多个原生的事件按照上面的解决方案那不是要在子组件监听多个原生事件并同时发射自定义事件?

于是,vue提供了一个$listeners属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。如果v-on="$listeners"将所有的事件监听器指向这个组件的某个特定的子元素。这时候再使用该组件时便可以对该组件内的某个特定元素支持的事件进行监听,这时候$listeners包含了该元素支持的所有的监听器。另外不需要修饰符.native就可以进行监听了。

<template>
  <label>
    {{ label }}
    <input
      type="input"
      :value="value"
      v-on="$listeners"
    >
  </label>
</template>

<script>
export default {
  props: {
    label: {
      type: String,
      default: ''
    },
    value: {
      type: String,
      default: ''
    }
  },
};
</script>

<style></style>

<input-label @input="onInput" label="点击"></input-label>
<input-label @focus="onFocus"></input-label>
复制代码

这时候就可以实现监听了,子组件无需发射自定义事件。

同样,vue提供了$attrs property,它是一个对象,里面包含了作用在这个组件上的所有属性,配合v-bind="$attrs"将所有的属性指向这个组件的某个特定的子元素。在使用该组件的时候可以直接绑定该属性传值,而无需在组件内部定义props来接收。

事实上,v-on="$listeners"v-bind="$attrs"对于组件封装是十分有用的,它不仅可以作用于组件内的某一特定元素,对于子组件也是有用的。比如,我们基于element组件去二次封装一个组件时候想全盘使用element现有组件的所有监听器和属性时,不需要对原有的属性进行props声明,也不需要对原有的监听器进行$emit发射。比如:

<template>
  <el-date-picker
    type="date"
    clearable
    format="yyyy/MM/dd"
    value-format="yyyy-MM-dd"
    v-bind="$attrs"
    v-on="$listeners"
  ></el-date-picker>
</template>

<script>
/**
 * 简易的日期选择器
 * - 规定统一的日期格式化规则
 */
export default {};
</script>
复制代码

在使用这个二次封装的组件时,可以直接使用el-date-picker提供的所有属性和监听器。

分类:
前端
标签:
分类:
前端
标签: