扩展Vue.js SFC组件

时间:2019-07-11 17:50:18

标签: vue.js vue-component

在扩展Vue.js组件方面我找不到很好的资源。在我从事的每个项目中,无论使用什么UI组件库,都存在Application Base组件,这些组件扩展了UI库组件以实施公司/应用程序的默认值和标准。

我正在尝试扩展Vue-Multiselect:https://vue-multiselect.js.org/,它具有大约30个道具和12个插槽。我要扩展的组件无关紧要-我只提到它是因为理想情况下我不想在实现中重复30个prop和12个插槽。

我只想对组件的行为进行两项更改:

使disabled变得更聪明

Vue-Multiselect组件具有一个标准disabled道具,该道具可以按预期工作:

<Multiselect :disabled="isDisabled" ...>

在我们的应用程序中,我们在Vuex中具有全局状态,该状态确定该应用程序是否为只读。我要避免的是要求开发人员将此状态传递给每个表单字段:

<Multiselect :disabled="readOnly || isDisabled" ...>
<OtherComponent :disabled="readOnly || someOtherCondition" ...>
...

因此,基本组件的用户只需要担心其本地UI状态会影响禁用状态:

<BaseCombo :disabled="!emailValid" ...>

这将处理90%的表单字段(当应用程序为只读状态时被锁定)的情况,在我们要忽略全局只读状态的情况下,我可以使用其他支持。

<BaseCombo :disabled="!emailValid" :ignoreReadOnly="true" ...>

提供默认值

第二,我只想覆盖一些默认的prop值。这篇文章解决了提供默认值的问题:

https://stackoverflow.com/a/52592047/695318

这非常有效,直到我尝试修改前面提到的禁用道具的行为为止。


我试图解决这个问题的方法是包装或扩展组件。如果可能的话,我真的想避免重新声明所有道具。

<template>
  <Multiselect
    :disabled="myCustomDisabled"
    :value="value"
    @input="$emit('input', $event)"
    :options="options"
    :label="label"
    :track-by="trackBy"
    :placeholder="placeholder"
    ... repeat for all 30 options

<script>
import Multiselect from 'vue-multiselect'

export default {
  name: "BaseCombo",
  extends: Multiselect, // extend or simply wrap?
  computed: {
    myCustomDisabled() {
      this.props.disabled || ... use disabled from Vuex state
    }
  },
  props: {
    disabled: Boolean,
    placeholder: {
      type: String,
      default: 'My Default Value',
    },
  ... repeat for all props

我遇到的问题是我不知道如何处理插槽。该BaseCombo的用户​​仍应能够使用VueMultiselect组件中的所有12个插槽。

是否有更好的扩展组件解决方案?

3 个答案:

答案 0 :(得分:1)

组件的组成更加灵活,但是对于任何想使用更简单的extends选项(请参见Extending VueJS Components)的人来说,这就是一个例子。

注释

  • 默认情况下,VueMultiselect的道具全部向下传递,但我们会覆盖disabled
  • 省略扩展中的模板以继承VueMultiselect模板
  • scoped的形式传递VueMultiselect样式表(默认情况下不继承)
  • 插槽正常工作
  • 听众可以正常工作
<script>
import VueMultiselect from 'vue-multiselect'

export default {
  name: 'MultiselectExtended',
  extends: VueMultiselect,
  props: {
    disabled: {
      type: Boolean,
      default: function() {
        return this.$store.getters.isReadOnly
      }
    },
  },
}
</script>

<style src="vue-multiselect/dist/vue-multiselect.min.css" scoped></style>

答案 1 :(得分:0)

您可以使用this.$props访问props属性中定义的props。同样,您可以使用this.$attrs访问属性(尚未定义为道具的事物)。最后,您可以将道具与v-bind="someVariable"绑定。

如果结合使用此功能,则可以执行以下操作:

<!-- App.vue -->
<template>
  <component-a msg="Hello world" :fancy="{ test: 1 }" />
</template>
<!-- ComponentA.vue -->
<template>
  <component-b v-bind="$attrs" />
</template>

<script>
export default {
  name: 'componentA'
}
</script>
<!-- ComponentB.vue -->
<template>
  <div>
    {{ msg }}
    {{ fancy }}
  </div>
</template>

<script>
export default {
  props: {
    msg: String,
    fancy: Object
  },
  mounted () {
    console.log(this.$props);
  }
}
</script>

在此示例中,组件B将是您尝试扩展的组件。

答案 2 :(得分:0)

这是一个基于Sumurai8的回答和motia的评论的完整示例。

<template>
  <Multiselect v-bind="childProps" v-on="$listeners">
    <slot v-for="(_, name) in $slots" :name="name" :slot="name" />
    <template v-for="(_, name) in $scopedSlots" :slot="name" slot-scope="slotData">
      <slot :name="name" v-bind="slotData" />
    </template>
  </Multiselect>
</template>

<script>
  import Multiselect from 'vue-multiselect'

  export default {
    name: "BaseCombo",
    props: {
      placeholder: {
        type: String,
        default: 'This is my default',
      },
      disabled: {
        type: Boolean,
        default: false,
      },
    },
    components: {
      Multiselect,
    },
    computed: {
      childProps() {
        return { ...this.$props, ...this.$attrs, disabled: this.isDisabled };
      },
      appReadOnly() {
        return this.$store.state.appReadOnly;
      },
      isDisabled() {
        return this.disabled || this.appReadOnly;
      }
    },
  }
</script>