Vue 3 Composition API - 类似选项卡的组件中的“子"数据 [英] Vue 3 Composition API - 'child' data in a tabs-like component

查看:44
本文介绍了Vue 3 Composition API - 类似选项卡的组件中的“子"数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

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

<轮播面板>面板内容</轮播面板></carousel-group>

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

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

每个carousel-panel 都有实例数据,例如active,这是我想要访问的.但是通过默认插槽获取面板不会附带任何这些属性.

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

解决方案

您要做的就是所谓的复合组件".你需要使用 provide/inject(相当于 Vue 中的 React Context)并让 carousel-group 知道里面已经放了一个 carousel-panel.

我在表组件中使用 registerChild 来执行此操作.我从 https://github.com/3nuc/vue-compound-components/tree/master/example-table

<脚本设置>从'vue'导入{ref,提供,defineProps}const props =defineProps({项目: {类型:数组,要求:真实,}})const registerChild = (child) =>已注册的Children.value.push(child);const unregisterChild = (child) =>{已注册的Children.value =已注册的Children.value.filter(注册儿童 =>注册孩子!== 孩子);};constregisteredChildren = ref([]);提供('TheParent', { registerChild, unregisterChild })<模板>列:{{registeredChildren.map(x=>x.itemPropertyName)}}<表格><头><tr><th v-for=registeredChildren 中的列":key=列"><component :is="column.content";ref=列"/>{{ column.sortable ?'可排序':''}}</th></tr></thead><tr v-for="item, i in items";:key="i"><td v-for="column, j inregisteredChildren";:key=j">{{ item[column.itemPropertyName] }}</td></tr></tbody><!-- 我们希望mounted() 生命周期在插槽中的组件上触发,但我们不想显示它们--><div v-show="false"><插槽/>

</模板>

<脚本设置>从'vue'导入{onMounted,onBeforeUnmount,inject,defineProps,h,render,useContext}const props =defineProps({项目属性名称:{类型:字符串,要求:真实,},可排序:{类型:布尔型,默认值:假}})const { registerChild, unregisterChild } = inject('TheParent');const ctx = useContext();const childInfo = {itemPropertyName: props.itemPropertyName,可排序:props.sortable,//因为 ctx.slots.default 是一个渲染 fn,//我们将插槽内容从这里传递给父组件//然后使用 <component :is=""/> 在父组件中渲染它内容:ctx.slots.default}registerChild(childInfo);onBeforeUnmount(() => {unregisterChild(childInfo);})<模板><div><插槽/>

</模板>

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

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

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

然后让 carousel-group 的内部数据属性从 registerChildren 的第一个窗格开始,然后在 5 秒后更改为第二个窗格,依此类推.

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

I have a TabGroup/TabItem component for both Vue 3 and Vue 2, but it's written in the Options API.

I'm creating a new carousel component, which shares a lot of the features, but I'm trying to write it in the Composition API as that's how we now write everything in our projects.

I am essentialy trying to access data on the child components, but I don't see an obvious way of doing it. I want to be able to determine which of the children is active within the group component, so it can manage things like keyboard navigation and so on.

  • I could use Vuex, but this is a generic component that will be used more than one place. I could give each some kind of ID to differentiate them in the store, but this component is also eventually going into a library, which won't have access to the Vuex instance of whatever project is using it.
  • Events don't seem like they'll work, because of the way the components are set up - the wrapper doesn't know what the children are, and they're just populated into the default slot. It seems like events will fire where the carousel is used, not inside the group.
  • I can't use refs for the same reason, they will be available where the carousel is created, not in the carousel-group. Plus I don't want anyone to have to add a ref to every panel just to use the component.
  • I can't provide the panels as an array to carousel-group, because every panel can contain any content, and I want that to be as easy as possible to set up.

For some background, the basic setup is this:

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

As part of the setup process I can access the children, similar to what I would do in the Options API, and get a list of the child panels.

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

Each carousel-panel has instance data such as active, which is what I want to access. But getting the panels via the default slot doesn't come with any of those properties.

Any ideas would be great, as I'm a bit stumped at the moment. I don't really want to have to do this using the Options API.

解决方案

What you're trying to do is called "compound components". You need to use provide/inject (equivalent of React Context in Vue) and let carousel-group know that a carousel-panel has been put inside.

I do this with registerChild in a table component. I took this from my repo at https://github.com/3nuc/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>

Obviously this is a table component, but you get the idea - you can track which panels are inside a carousel-group by registering them on mount, using some property from the parent thanks to inject()

Another thing is that you want to manage which carousel-panel is active. Which panel is active would obviously be kept in carousel-group, but you also need to toggle visibility of panels using v-for="panes in registeredChildren" v-if="activePane === pane.id", but that can be achieved by something like:

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

Then have the carousel-group's internal data property start with the first pane from registeredChildren then after 5s change to second and so on.

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

这篇关于Vue 3 Composition API - 类似选项卡的组件中的“子"数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆