У меня есть компонент «календарь». Этот компонент будет получать некоторые данные от вызывающего абонента и отображать все дни в месяце — если для этого дня существуют какие-то данные, то я хочу затем отобразить другой компонент. Проблема в том, что мой компонент календаря не знает, какой тип компонента он должен отображать в каждом слоте дня.
Я ожидал, что смогу просто использовать универсальный компонент Vue3 и сказать:
Record<string, T[]>
(где ключ — это просто Y-m-d
).DefineComponent<T>
// Calendar.vue
<script setup lang = "ts" generic = "T">
import {computed, DefineComponent, ref} from "vue";
import CalendarDay from "./CalendarDay.vue";
type Props<T> = {
data: Record<string, T[]>,
component: DefineComponent<T>,
labelFn: (length: number, date: Date) => string,
};
const props = withDefaults(
defineProps<Props<T>>(),
{
labelFn: (count, date) => `${count} items on ${date.toLocaleDateString()}`
}
);
const currDate = new Date();
const currentValue = ref<{year: number, month: number}>(
{
year: currDate.getFullYear(),
month: currDate.getMonth() + 1
}
);
const dayArray = computed<number[]>(() => {
const date = new Date();
const {year, month} = currentValue.value;
const numbers: number[] = [];
date.setFullYear(year, month-1, 1);
while (date.getMonth() == month - 1) {
numbers.push(date.getDate());
date.setDate(date.getDate() + 1);
}
return numbers;
})
</script>
<template>
<div aria-live = "polite" class = "calendar-table">
<p v-for = "i in 7" :key = "i" class = "calendar-table-header" aria-hidden = "true">
<span class = "long-name">
{{(new Date(2022, 7, i)).toLocaleString(undefined, {weekday: "long"})}}
</span>
<span class = "short-name">
{{(new Date(2022, 7, i)).toLocaleString(undefined, {weekday: "short"})}}
</span>
</p>
<CalendarDay
v-for = "day in dayArray"
:label-fn = "props.labelFn"
:data = "props.data"
:component = "props.component"
:date = "new Date(Date.UTC(currentValue.year, currentValue.month - 1, day))"
/>
</div>
</template>
// CalendarDay.vue
<script setup lang = "ts" generic = "T">
import { computed, DefineComponent} from "vue";
type Props<T> = {
data: Record<string, T[]>,
component: DefineComponent<T>,
labelFn: (length: number, date: Date) => string,
date: Date
};
const props = defineProps<Props<T>>();
const dateStr = computed(() => `${props.date.toISOString().split('T')[0]}`);
const value = computed((): T[] => props.data[dateStr.value] ?? []);
const style = computed(() => props.date.getDate() == 1 ? {"grid-column-start": props.date.getDay()} : null)
</script>
<template>
<div class = "day" :class = "{'has-items': value.length > 0}" :style = "style" :aria-hidden = "value.length == 0">
<div class = "day-content" :aria-label = "labelFn(value.length, props.date)">
<p class = "date" aria-hidden = "true">
{{ props.date.getDate() }}
</p>
<template v-for = "val in value">
<component :is = "component" v-bind = "val"/>
</template>
</div>
</div>
</template>
Хотя это работает в браузере, компилятор машинописного текста жалуется:
Type 'DefineComponent<{}, {}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes<{}>>, {}, {}>' is not assignable to type 'DefineComponent<{}>'.ts(2322)
Calendar.vue(7, 5): The expected type comes from property 'component' which is declared here on type '{ data: Record<string, {}[]>; component: DefineComponent<{}>; labelFn: (length: number, date: Date) => string; } & VNodeProps & AllowedComponentProps & ComponentCustomProps & Record<...>'
Argument of type 'T' is not assignable to parameter of type '(CreateComponentPublicInstance<Readonly<T extends ComponentPropsOptions<Data> ? ExtractPropTypes<T> : T>, {}, {}, ComputedOptions, ... 15 more ..., {} & EnsureNonVoid<...>> extends { ...; } ? Props : any) & Record<...>'. Type 'T' is not assignable to type 'CreateComponentPublicInstance<Readonly<T extends ComponentPropsOptions<Data> ? ExtractPropTypes<T> : T>, {}, {}, ComputedOptions, ... 15 more ..., {} & EnsureNonVoid<...>> extends { ...; } ? Props : any'.ts(2345)
CalendarDay.vue(1, 34): This type parameter might need an `extends CreateComponentPublicInstance<Readonly<T extends ComponentPropsOptions<Data> ? ExtractPropTypes<T> : T>, {}, {}, ComputedOptions, ... 15 more ..., {} & EnsureNonVoid<...>> extends { ...; } ? Props : any` constraint.
CalendarDay.vue(1, 34): This type parameter might need an `extends (CreateComponentPublicInstance<Readonly<T extends ComponentPropsOptions<Data> ? ExtractPropTypes<T> : T>, {}, {}, ComputedOptions, ... 15 more ..., {} & EnsureNonVoid<...>> extends { ...; } ? Props : any) & Record<...>` constraint.
Игровая площадка Vue потребителя компонента находится здесь (на этой игровой площадке Vue)
Как правильно это ввести, чтобы компилятор мог убедиться, что данные соответствуют реквизитам для этого компонента?
Да, я получаю то же самое на местном уровне. Сообщения немного отличаются, но это те же строки, что и ошибка.
Как именно? С vue-tsc? Пожалуйста, добавьте в вопрос соответствующие части кода, он должен быть понятным, если внешняя ссылка недоступна.
Я полагаю, вы можете просто использовать тип компонента вместо DefineComponent<T>. Даже если бы то, что вы пробовали, поддерживалось, я сомневаюсь, что вы могли бы получить пользу от подробного типа, который предоставляет универсальный тип. Поддержка TS в шаблонах пока новая, но вы можете открыть проблему
Тип Component
работает нормально:
Календарь.vue
<script setup lang = "ts" generic = "T extends Record<string, any>">
import {type Component, computed, ref} from "vue";
import CalendarDay from "./CalendarDay.vue";
type Props<T> = {
data: Record<string, T[]>,
component: Component<T>,
labelFn: (length: number, date: Date) => string,
};
...
Таким образом, вы можете использовать его без каких-либо типов:
<script setup lang = "ts">
import Calendar from './Calendar.vue';
import SessionLink from "./SessionLink.vue";
//type Props = {days: Record<string, Array<InstanceType<typeof SessionLink>['$props']>>};
const props /*: Props */ = {
days: {
"2024-08-31": [
{
some: 'some',
data: 'data'
}
]
}
}
function label(count: number, date: Date) {
return `${count} sessions on ${date.toLocaleDateString()}`;
}
</script>
<template>
<Calendar :data = "props.days" :component = "SessionLink" :label-fn = "label" />
</template>
Если вы укажете неправильные типы, :component
будет ошибочным, если вы хотите более точный контроль, используйте
type Props = {days: Record<string, Array<InstanceType<typeof SessionLink>['$props']>>};
поэтому неправильные типы будут содержать ошибки в данных.
Можете ли вы подтвердить, что проблема существует в сборке? Потому что это может быть связано с редактором, в котором вы видите ошибку.