Vue 3 Composition API - 类似标签的组件中的“子”数据

Posted

技术标签:

【中文标题】Vue 3 Composition API - 类似标签的组件中的“子”数据【英文标题】:Vue 3 Composition API - 'child' data in a tabs-like component 【发布时间】: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属性。 【参考方案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....来控制活动的孩子。

以上是关于Vue 3 Composition API - 类似标签的组件中的“子”数据的主要内容,如果未能解决你的问题,请参考以下文章

Vue 3 Composition API实战前瞻

Vue 3 Composition API 实战前瞻

为啥在 Vue 3 的 Composition API 中使用“API”这个词?

Vue3新特性——Composition API详解

Vue 3 Composition API - watchEffect 与 watch

围绕Vue 3 Composition API构建一个应用程序,包含一些最佳实践!