Как глобально зарегистрировать компонент Vue 3 с API композиции?

Я создаю библиотеку компонентов с Vue 3 и API композиции. Библиотека компонентов будет загружена в приложение NuxtJS в виде плагина. Как правильно глобально зарегистрировать компоненты (написанные в соответствии с API композиции) в этом плагине?

./stories/Panel.vue (очень урезанная версия):

<template>
    <div class = "panel">
        <div class = "header" v-if = "title">{{ title }}</div>
        {{ content }}
    </div>
</template>

<style>
.panel .header {
    background-color: #7f7f7f;
    color: #fff;
}
</style>

<script setup lang = "ts">
interface Props {
    title?: string;
    content?: string;
}

const name = 'my-panel';
const props = defineProps<Props>();
</script>

components.ts (содержит все компоненты):

import Panel from './stories/Panel.vue';
// all other components are imported and exported as well

export {
    Panel
};

lib.ts:

import * as components from './components';
import { App } from '@vue/runtime-core';

const plugin = {
    install(Vue: App) {
        Object.values(components).forEach((component) => {
            // Is there a way to globally register a component in a way
            // that the name, defined in the composition API is used?
            Vue.component(component);
        });
    },
};

export default plugin;
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
0
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот общий метод с некоторыми вариантами. Вы можете определить особенности, исходя из того, что лучше всего подходит для вас.

Принцип

Переменные, экспортированные в обычный блок <script>, можно импортировать отдельно в сам компонент, и все, что определено в этом блоке сценария, также доступно в блоке <script setup>.

Метод

Компоненты

Компоненты должны иметь метаданные, экспортированные из обычного блока <script>. Здесь у вас есть два варианта: экспортировать имя напрямую (без шаблона) или экспортировать объект метаданных (рекомендуется, если вам нужно больше метаданных, чем просто имя).

Только с именем:

Panel.vue

<script lang = "ts">
export const name = "my-panel";
</script>

<script setup lang = "ts">
console.info(name) // `name` is available here.
</script>

С объектом метаданных:

Panel.vue

<script lang = "ts">
export const metadata = {
  name: "my-panel",
};
</script>

<script setup lang = "ts">
console.info(metadata.name) // `metadata` is available here.
</script>

Индекс

К сожалению, эта часть довольно многословна по сравнению с оригиналом.

Просто имя

Имена должны быть каким-то образом включены в экспорт. Это потребует использования экспорта одного значения, поскольку мы не просто экспортируем существующую привязку (например, переменную или импортированные компоненты). Поскольку в данном случае это просто имя, подойдет простой объект с вычисляемыми ключами свойств. При желании вы также можете использовать один из методов «массива верхнего уровня», показанных в разделе «Объект метаданных».

components.ts

// Import the metadata as well.
import Panel, { name as PanelName } from './stories/Panel.vue';

// The export can be default or named.
export default {
  [PanelName]: Panel,
}

Объект метаданных

Если вам нужно только имя, вы можете использовать метод из предыдущего раздела.

components.ts

// Import the metadata as well.
import Panel, { metadata as PanelMetadata } from './stories/Panel.vue';

// This works the same as in the previous section.
// As before, the export can be default or named.
export default {
  [PanelMetadata.name]: Panel,
}

Однако с объектом метаданных вы, вероятно, захотите, чтобы весь объект был включен в экспорт.

Всего у вас есть четыре варианта. Для верхнего уровня вы можете использовать объект с именем из объекта метаданных или массив. Для значений вы можете использовать объект с ключами, являющимися именами из объектов метаданных, и значениями, являющимися объектом, содержащим компонент и объект метаданных, или вы можете использовать массив. Эти варианты можно смешивать и сочетать.

Использование массивов для значений требует, чтобы они были as const, чтобы TypeScript мог вывести правильный тип.

// Array + array
export default [
  [Panel, PanelMetadata] as const,
];

// Array + object
export default [
  { component: Panel, metadata: PanelMetadata },
]

// Object + array
export default {
  [PanelMetadata.name]: [Panel, PanelMetadata] as const,
};

// Object + object
export default {
  [PanelMetadata.name]: { component: Panel, metadata: PanelMetadata },
};

Плагин

То, что вы здесь используете, зависит от того, используете ли вы имя или объект метаданных, а также от метода, который вы использовали для экспорта компонентов.

Только для имени и простого объекта:

lib.ts:

// Assuming a default export.
import components from './components';
import { App } from '@vue/runtime-core';

const plugin = {
    // This is the app instance created by `createApp`.
    install(app: App) {
        // Get the `(key, value)` pairs of the export 
        // and register a component for each of them.
        Object.entries(components).forEach(([name, component]) => {
            app.component(name, component);
        });
    },
};

export default plugin;

Для одного из других методов экспорта:

// Array + array
components.forEach(([component, metadata]) => {
    app.component(metadata.name, component);
});

// Array + object
components.forEach(({ component, metadata }) => {
    app.component(metadata.name, component);
});

// Object + array
Object.values(components).forEach(([component, metadata]) => {
    app.component(metadata.name, component);
});

// Object + object
Object.values(components).forEach(({ component, metadata }) => {
    app.component(metadata.name, component);
});

Вероятно, вы можете увидеть шаблоны в параметрах итерации и обратного вызова.

Вы также можете использовать Object.entries для объектов верхнего уровня, но это немного менее чисто:

// For array values
Object.entries(components).forEach(([name, componentData]) => {
    app.component(name, componentData[1]);

// For object values
Object.entries(components).forEach(([name, componentData]) => {
    app.component(name, componentData.component);
});

Документация

Из документации API для app.component():

Регистрирует глобальный компонент, если передается и строка имени, и определение компонента, или извлекает уже зарегистрированный, если передается только имя.

См. документы по регистрации компонентов для получения дополнительной информации.

Дело в том, что я хочу использовать не ключ из components.ts, а name-свойство из Panel.vue, которое может отличаться, и я бы предпочел не поддерживать его в двух местах.

Codekie 18.01.2023 11:58

Код в <script setup> запускается только при создании компонента (событие жизненного цикла), поэтому нет возможности получить эти данные при определении компонента. У меня есть два рабочих решения. Если вы обновите свой вопрос, чтобы уточнить часть имени, я отредактирую свой ответ.

Darryl Noakes 18.01.2023 19:09

Я переписал свой ответ, @Codekie. Скажи мне, если это поможет в любом случае.

Darryl Noakes 18.01.2023 21:09

Когда я экспортирую name в обычный script-блок, я получаю Module '"./stories/Panel"' has no exported member 'name' от компилятора Typescript

Codekie 19.01.2023 17:10

Это странно. Я попробовал сейчас, и это работает нормально. Не могли бы вы как-нибудь поделиться соответствующим кодом? Может быть, отредактировать это в своем вопросе?

Darryl Noakes 19.01.2023 18:18

Неважно .. Когда я настроил репо с MRE, это сработало. Я, наверное, забыл export в прошлый раз. Спасибо, за ваш развернутый ответ.

Codekie 23.01.2023 21:55

Другие вопросы по теме