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

Liu*_*uka 6 communication parent-child slot vue-component vuejs2

我正在编写一小部分 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>
Run Code Online (Sandbox Code Playgroud)

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

和js:

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

然后是触发更改 onmouseover 的子组件:

<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>
Run Code Online (Sandbox Code Playgroud)

然后我会像这样使用这个组件:

<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>
Run Code Online (Sandbox Code Playgroud)

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

//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>
Run Code Online (Sandbox Code Playgroud)

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

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

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

这看起来很酷,但我不明白这是否是使用提供/注入的方式,或者是否可以在任何地方使用它(主要是性能方面,我实际上会滥用它)来创建父级 <->槽通信,当然,槽在语义上是链接到父级的

编辑 1

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

父母/孩子

但这在 whit slot 中不起作用,因为 Vue 处理组件范围的方式。鉴于我的示例 Preview 不是覆盖的父级,因为它没有直接嵌套在组件模板中。相反,如果我写这样的东西:

<template>
  <div class="preview" :class="{active: active}">
    <content>...<content> <!-- changes here -->
    <overlay>...</overlay> <!-- and here -->
  </div>
</template>
Run Code Online (Sandbox Code Playgroud)

Overlay 和 Content 可以自由地与 Preview 进行通信,只需发出事件即可。但是通过插槽,就像我之前提出的第一个示例一样,内容和覆盖(和预览)都是通用应用程序内容的子项,因此发出不会触发预览,而是应用程序(或包含预览组件的任何内容) ; 所以我需要一种新的方式来从插槽通信到父级,反之亦然。

关于这个主题的主线程: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>
Run Code Online (Sandbox Code Playgroud)

我的问题是: 是的提供/注入一个很好的方式来处理这个案件?slot-scope 是否更适合,即使 imho 破坏封装并且它很冗长?或者还有其他方法可以在不放弃插槽提供的自定义级别的情况下实现这种“策略模式”?

IVO*_*LOV 2

您可以简单地将子进程的上下文注入到插槽中,然后从此上下文中发出事件:

// 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>
Run Code Online (Sandbox Code Playgroud)