vue 怎么封装一个组件

一、组件基础:prop、slot、event

一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API。如果开发的是一个通用组件,那一定要事先设计好这三部分,因为组件一旦发布,后面再修改 API 就很困难了,使用者都是希望不断新增功能,修复 bug,而不是经常变更接口。如果你阅读别人写的组件,也可以从这三个部分展开,它们可以帮助你快速了解一个组件的所有功能。

属性 prop

prop(官方文档)定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。 props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值。一个简单的按钮组件: <wy-button/>

<template>
  <button :class="'wy-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>

使用组件:

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

在使用组件时,也可以传入一些标准的 html 特性,比如 id、class:

<wy-button id="btn" class="btn-submit"></wy-button>

这样的 html 特性,在组件内的 <button>元素(根元素)上会继承,并不需要在 props 里再定义一遍。这个特性是默认支持的,如果不期望开启,在组件选项里配置 inheritAttrs: false就可以禁用。

插槽slot

插槽 slot(官方文档),它可以分发组件的内容,比如在上面的按钮组件中定义一个插槽:

<template>
  <button :class="'wy-button-size' + size" :disabled="disabled">
    <slot name="icon"></slot>
    <slot>默认内容</slot>
  </button>
</template>

使用:

<wy-button>
    <template v-slot:icon>
        <i class="submit"></i>
    </template>
    提交
</wy-button>
自定义事件

官方文档

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

使用:

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

注意:如果不写 .native 修饰符,那么 @click 就是自定义事件 click,不是原生事件 click

自定义组件的v-model

v-model (官方文档)实际上可以说是props 结合event 的语法糖:组件内的<input> 默认需要提供value 属性的props 以及需要将新的值通过input 事件抛出。可通过model 选项改变默认值。

<template>
    <input
        type="text"
        :value="value"
        @input="$emit('input', $event.target.value)"
    />
</template>
<script>
  export default {
    props: {
        value: String,
    },
    // 默认值
    model: {
        prop: 'value',
        event: 'input',
    },
  }
</script>

使用:

<wy-input v-model="inputValue"></wy-input>

二、组件的通信 1:provide / inject

一般来说,组件之间的关系大概有以下几种:

A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。组件间经常会通信,Vue.js 内置的通信手段一般有两种:

  • ref : 给元素或组件注册引用信息.
  • $parent / $children :访问父 / 子实例。

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。但是这两种方式的弊端就是无法跨级或者兄弟间通信。通常的解决方案是引入Vuex或者Bus的解决方案。但是在开发独立组件时是不可取的。

provide / inject

官方文档

provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。

虽然provide / inject 官方并不推荐使用在应用程序中,但是在很多组件库诸如iView以及ElementUi都有大量用,因此在开发独立组件 时还是非常好用的。

下面是一个基础用法。假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件。

// A.vue
export default {
  provide: {
    name: 'zyh'
  }
}

// B.vue
export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // zyh
  }
}

模拟Vuex的用法:把整个 app.vue 实例通过 provide 对外提供

<template>
  <div>
    <router-view></router-view>
  </div>
</template>
<script>
  export default {
    provide () {
      return {
        app: this
      }
    }
  }
</script>

把整个 app.vue 的实例 this 对外提供,命名为 app(这个名字可以自定义)。接下来,任何组件(或路由)只要通过 inject 注入 app.vue 的 app 的话,都可以直接通过 this.app.xx 来访问 app.vue 的 data、computed、methods 等内容。

独立组件使用 provide / inject 的场景,主要是具有联动关系的组件,比如下面要介绍的具有数据校验功能的表单组件 Form。它其实是两个组件,一个是 Form,一个是 FormItem, Form 和 FormItem 不一定是父子关系,中间很可能间隔了其它组件,所以不能单纯使用 $parent 来向上获取实例 。FormItem要取得Form 组件上的一些特性(props),所以就需要得到父组件 Form。

三、组件的通信 2:派发与广播—自定义实现dispatchbroadcast方法

provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:

  • 父组件向子组件(支持跨级)传递数据

  • 子组件向父组件(支持跨级)传递数据

因此要实现的dispatchbroadcast方法将具有以下功能:

  • 在子组件调用 dispatch方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件。

  • 相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。

具体实现方式如下:假设有A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:

<!-- A.vue -->
<template>
	<button @click="handleClick">触发事件</button>
</template>
<script>
  import Emitter from '../mixins/emitter.js';
  
  export default {
    name: 'componentA',
    mixins: [ Emitter ],
    methods: {
      handleClick () {
        this.broadcast('componentB', 'on-message', 'Hello Vue.js');
      }
    }
  }
</script>
// B.vue
export default {
  name: 'componentB',
  created () {
    this.$on('on-message', this.showMessage);
  },
  methods: {
    showMessage (text) {
      window.alert(text);
    }
  }
}
function broadcast(componentName, eventName, params) {
  this.$children.forEach(child => {
    const name = child.$options.name;

    if (name === componentName) {
      child.$emit.apply(child, [eventName].concat(params));
    } else {
      broadcast.apply(child, [componentName, eventName].concat([params]));
    }
  });
}
export default {
  methods: {
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.name;

      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;

        if (parent) {
          name = parent.$options.name;
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
    broadcast(componentName, eventName, params) {
      broadcast.call(this, componentName, eventName, params);
    }
  }
};

四、一个带有校验功能的Form组件

Form 组件的核心功能是数据校验,一个 Form 中包含了多个 FormItem,当点击提交按钮时,要逐一对每个 FormItem 内的表单组件校验,而校验是由使用者发起,并通过 Form 来调用每一个 FormItem 的验证方法,再将校验结果汇总后,通过 Form 返回出去。大致的流程如下图所示:

具体代码实现点击这里查看

posted @ 2020-11-10 18:24  落忆红尘  阅读(677)  评论(0)    收藏  举报