【问题标题】:Vue 3 Composition API - 'child' data in a tabs-like componentVue 3 Composition API - 类似标签的组件中的“子”数据
【发布时间】:2021-10-24 19:10:59
【问题描述】:

我有一个适用于 Vue 3 和 Vue 2 的 TabGroup/TabItem 组件,但它是在 Options API 中编写的。

我正在创建一个新的轮播组件,它共享许多功能,但我正在尝试在 Composition API 中编写它,因为这就是我们现在在项目中编写所有内容的方式。

我基本上是在尝试访问子组件上的数据,但我没有看到明显的方法。我希望能够确定组组件中哪些子组件处于活动状态,以便它可以管理键盘导航等操作。

  • 我可以使用 Vuex,但这是一个通用组件,将在多个地方使用。我可以给每一个 ID 以在商店中区分它们,但这个组件最终也会进入一个库,它无法访问任何项目使用它的 Vuex 实例。
  • 事件似乎不起作用,因为组件的设置方式 - 包装器不知道子级是什么,它们只是填充到默认槽中。似乎事件会在使用轮播的地方触发,而不是在组内。
  • 出于同样的原因,我不能使用引用,它们将在创建轮播的地方可用,而不是在carousel-group 中。另外,我不希望任何人为了使用组件而必须向每个面板添加 ref。
  • 我无法将面板作为数组提供给carousel-group,因为每个面板都可以包含任何内容,我希望设置尽可能简单。

对于一些背景,基本设置是这样的:

<carousel-group>
    <carousel-panel>
        Panel content
    </carousel-panel>
</carousel-group>

作为设置过程的一部分,我可以访问子面板,类似于我在 Options API 中所做的,并获取子面板的列表。

panels.value = slots.default().filter(child => child.type.name === 'carousel-panel');

每个carousel-panel都有active之类的实例数据,这是我要访问的。但是通过默认插槽获取面板并没有任何这些属性。

任何想法都会很棒,因为我现在有点难过。我真的不想使用 Options API 来执行此操作。

【问题讨论】:

  • $children 无论如何都是removed in Vue 3...
  • But those panels don't have any of the associated data being provided by the carousel-panel 的“数据”是指“插槽内容”,对吧?我不知道您是尝试访问嵌套实例属性还是仅访问插槽内容(“HTML”)
  • 实例属性,将从setup 函数返回。比如每一个都有一个内部ID和一个active属性。

标签: vue.js vuejs3


【解决方案1】:

您正在尝试做的事情称为“复合组件”。 你需要使用 provide/inject(相当于 Vue 中的 React Context)并让 carousel-group 知道里面已经放了一个 carousel-panel。

我在表格组件中使用 registerChild 执行此操作。我从https://github.com/sethidden/vue-compound-components/tree/master/example-table的仓库中获取了这个

<!-- MyTable.vue -->
<script setup>
import {ref, provide, defineProps } from 'vue'

const props = defineProps({
  items: {
    type: Array,
    required: true,
  }
})

const registerChild = (child) => registeredChildren.value.push(child);
const unregisterChild = (child) => {
  registeredChildren.value = 
    registeredChildren.value.filter(
      registeredChild => registeredChild !== child
    );
};
  
const registeredChildren = ref([]);

provide('TheParent', { registerChild, unregisterChild })
  
</script>
<template>
columns: {{ registeredChildren.map(x=>x.itemPropertyName) }}
<table>
  <thead>
    <tr>
      <th v-for="column in registeredChildren" :key="column">
        <component :is="column.content" ref="column"/>
        {{ column.sortable ? 'sortable' : ''}}
      </th>
    </tr>
  </thead>
  <tbody>
    <tr v-for="item, i in items" :key="i">
      <td v-for="column, j in registeredChildren" :key="j">
        {{ item[column.itemPropertyName] }}
      </td>
    </tr>
  </tbody>
</table>

<!-- we want the mounted() lifecycle to fire on the components that are in the slot, but we dont want to show them -->
<div v-show="false">
  <slot/>
</div>
</template>

<!-- MyTableColumn.vue -->
<script setup>
import {onMounted,onBeforeUnmount,inject, defineProps, h, render, useContext} from 'vue'

const props = defineProps({
  itemPropertyName: {
    type: String,
    required: true,
  },
  sortable: {
    type: Boolean,
    default: false
  }
})
const { registerChild, unregisterChild } = inject('TheParent');
const ctx = useContext();

const childInfo = {
  itemPropertyName: props.itemPropertyName,
  sortable: props.sortable,

  // since ctx.slots.default is a render fn,
  // we pass slot content from here to the parent component
  // then render it in the parent using <component :is=""/>
  content: ctx.slots.default
}

registerChild(childInfo);
onBeforeUnmount(() => {
  unregisterChild(childInfo);
})

</script>
<template>
<div>
  <slot/>
</div>
</template>

显然这是一个表格组件,但您明白了 - 您可以通过在装载时注册它们来跟踪哪些面板位于轮播组内,感谢注入()使用父级的一些属性

另一件事是您要管理哪个轮播面板处于活动状态。哪个面板处于活动状态显然会保留在轮播组中,但您还需要使用 v-for="panes in registeredChildren" v-if="activePane === pane.id" 切换面板的可见性,但这可以通过以下方式实现:

<carousel-group>
  <carousel-pane id="1"/>
  <carousel-pane id="2"/>
  <carousel-pane id="3"/>
</carousel-group>

然后让轮播组的内部数据属性从 registerChildren 的第一个窗格开始,然后在 5 秒后更改为第二个,依此类推。

https://github.com/3nuc/vue-compound-components/blob/master/example-dropdown/Example.vue

【讨论】:

  • 我看到你也回复了我上面关于 instancec 属性的评论。您可以将所需的子实例属性放入const childInfo = 对象中,然后使用它们在父对象中注册子对象。在我上面的例子中,你肯定需要在 childInfo 中传递“id”属性,这样你就知道在 v-if 中显示哪个窗格)
  • 有趣。我以前从未尝试过提供/注入,但这似乎是为这种情况而设计的。我相信提供/注入是反应性的,所以我可以摆脱在该组件内呈现窗格的内容吗?我试试看。
  • 是的,注入/提供是一种方法。顺便说一句,您不需要在面板上使用id。只需在面板中创建一个isActive ref 并在注册时将其传递给一个组。家长现在可以通过设置其isActive.value....来控制活动的孩子。
猜你喜欢
  • 1970-01-01
  • 2022-12-04
  • 2021-06-24
  • 2022-01-26
  • 2021-05-31
  • 2022-11-29
  • 1970-01-01
  • 2021-09-08
  • 2020-06-16
相关资源
最近更新 更多