Vue.js-父<->插槽通讯

时间:2018-07-19 16:35:40

标签: vuejs2 parent-child vue-component communication slot

我正在编写一小部分Vue组件,以创建要在将来的项目中使用的库,但是我对这个主题感到很困惑。也许我需要一种完全不同的方法,但是我不知道...

我从策略模式中汲取灵感(我认为是这样):您创建一个模板组件,其行为取决于作为参数传递的嵌套组件。例如,我创建了一个Preview组件,该组件拥有一种更改bkg图像的方法,并且我想在此组件中嵌套一个覆盖层,以调用此方法。由于该覆盖层可以是所有东西,因此我认为如果它是通过插槽嵌套的,那就太好了:

<template>
  <div class="preview" :class="{active: active}">
    <div class="content">
      <slot name="content"></slot>
    </div>
    <div class="overlay"><slot></slot></div>
    </div>
</template>

(我将通过img列表将内容设为v-for)

还有js:

props: {
    content: {default: function () { return [] }}
  },
  data: function () {
    return {
      preview: null
    }
  },
  methods: {
    setPreview: function (e) {
      this.preview = e
    }
  }
}

然后有一个子组件在鼠标悬停时触发更改:

<template>
  <div @mouseover="set">
    <slot></slot> <!-- some random content -->
  </div>
</template>

<script>
export default {
  props: ['target']
  methods: {
    set: function () {
      // figure a way to call "parent" setPreview
    }
  }
}
</script>

然后我将像这样使用该组件:

<preview>
  <template slot="content">... a bounch of v-if bound images</template>
  <template>
    <change-preview-onover target="first-img">...</change-preview-onover>
    <change-preview-onclick target="second-img">...</change-preview-onclick> <!-- different policy -->
  </template>
</preview>

我尝试了两种不同的方法:作用域插槽和提供/注入。 使用作用域限定的插槽,我得到的是这样的:

//preview
<template>
  <div class="preview" :class="{active: active}">
    <div class="content">
      <slot name="content"></slot>
    </div>
    <div class="overlay" :callback="{setPreview}"><slot></slot></div>
    </div>
</template>
//js...

//overlay element
<template>
  <div @mouseover="set">
    <slot></slot> <!-- some random content -->
  </div>
</template>
<script>
export default {
  props: ['target', 'callback']
  methods: {
    set: function () {
      this.callback.setPreview(this.target)
    }
  }
}
</script>

//usage
<preview>
  <template slot="content">... a bounch of v-if bound images</template>
  <template slot-scope={callback}>
    <change-preview-onover :callback="callback" target="first-img">...</change-preview-onover>
    <change-preview-onclick :callback="callback" target="second-img">...</change-preview-onclick>
  </template>
</preview>

我不喜欢这种方式,因为它破坏了封装(用户必须知道回调的存在,并将其传递给所有变更预览组件),并且获得了很多冗余代码。我试图在覆盖组件内部移动插槽范围,但没有任何运气。 因此,我已经阅读了有关提供/注入的信息,基本上现在我是这样的:

//preview.js
provide: function () {
  return {
    setPreview: this.setPreview
  }
}

//overlay.js
inject: ['setPreview'],
props: ['target'],
methods: {
  set: function () {
    this.setPreview(this.target)
  }
}

这看起来很酷,但是我不知道这是要使用provide / inject的方式,还是可以在任何地方使用它(主要是性能方面的考虑,我会滥用它)来创建父对象<->插槽通讯,当然,插槽是在语义上链接到父级的

编辑1

在Vue.js中,有一种处理父子沟通的标准方法:

parent/child

但是,由于Vue如何处理组件范围,因此无法使用该插槽。在我的示例中,Preview不是叠加层的父对象,因为它没有直接嵌套在组件模板中。相反,如果我写这样的话:

<template>
  <div class="preview" :class="{active: active}">
    <content>...<content> <!-- changes here -->
    <overlay>...</overlay> <!-- and here -->
  </div>
</template>

覆盖图和内容可以与预览发出的事件自由通信。 但是整个插槽,就像我之前提出的第一个示例一样,内容和覆盖(和预览)都是通用App内容的子级,因此,发射不会触发Preview,而是触发App(或包含预览组件的任何对象) ;所以我需要一种新的方式来从广告位到父级进行通信,反之亦然。

有关此主题的主线程:https://github.com/vuejs/vue/issues/4332 在这里,他们使用了作用域限定的插槽(好的,但是很糟糕)或$ parent,我不能使用它,因为它需要该插槽是父级的直接子级,但并不总是如此,也许我想添加一个过渡或其他内容,得到这样的东西:

//Modal
<div>
  <tr-fade> <!-- tr-fade is a registered comopnent and so it's the $parent of slot -->
    <slot></slot>
  </tr-fade>
</div>

我的问题是: provide/inject是处理这种情况的好方法吗?即使imho破坏封装并且很冗长,裂隙镜是否也更合适?还是有其他方法可以实现这种“策略模式”而不放弃广告位提供的定制级别?

1 个答案:

答案 0 :(得分:1)

您只需在插槽中插入孩子的上下文,然后从该上下文发出事件:

// the child
<template>
  <div>
    <slot :context="thisContext"/>
  </div>
</template>

<script>
export default
{
  computed:
  {
    thisContext()
    {
      return this;
    }
  }
}
</script>

// the parent
<template>
  <child @custom_event="handleCustom">
    <template slot-scope="ctx">
      <button @click="sendClick(ctx)">Click me</button>
    </template>
  </child>
</template>

<script>
export default
{
  methods:
  {
    sendClick(ctx)
    {
      ctx.$emit('custom_event', {custom_data: 3});
    },
    handleCustom(payload)
    {
      console.log("Custom payload:", payload);
    }
  }
}
</script>