迁移到v4

将您的应用程序从Nuxt UI v3迁移到Nuxt UI v4的全面指南。

Nuxt UI v4标志着一个重要的里程碑:Nuxt UI和Nuxt UI Pro现在统一为一个完全开源的免费库。您现在可以访问125个以上的生产就绪组件,所有组件都在@nuxt/ui包中提供。

Nuxt UI v4需要Nuxt 4.1或更高版本的,因为存在一些依赖关系。请确保在迁移到Nuxt UI v4之前进行升级。

本指南提供了将应用程序从v3迁移到v4的分步说明。

迁移您的项目

来自Nuxt UI Pro

  1. 将package.json中的@nuxt/ui-pro替换为@nuxt/ui:
pnpm remove @nuxt/ui-pro
pnpm add @nuxt/ui tailwindcss
  1. 在nuxt.config.ts中将@nuxt/ui-pro替换为@nuxt/ui:
nuxt.config.ts
export default defineNuxtConfig({
  modules: [
-   '@nuxt/ui-pro',
+   '@nuxt/ui'
  ]
})
  1. 在vite.config.ts中将@nuxt/ui-pro替换为@nuxt/ui:
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
- import uiPro from '@nuxt/ui-pro/vite'
+ import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
-   uiPro({
+   ui({
      ui: {
        colors: {
          primary: 'green',
          neutral: 'slate'
        }
      }
    })
  ]
})
  1. 在app.config.ts中使用ui密钥而不是uiPro:
app/app.config.ts
export default defineAppConfig({
  ui: {
    colors: {
      primary: 'green',
      neutral: 'slate'
    },
+   pageCard: {
+     slots: {
+       root: 'rounded-xl',
+     }
+   }
  },
- uiPro: {
-   pageCard: {
-     slots: {
-       root: 'rounded-xl',
-     }
-   }
- }
})
  1. 在vite.config.ts中使用ui键而不是uiPro:
vite.config.ts
export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        colors: {
          primary: 'green',
          neutral: 'slate'
        },
+       pageCard: {
+         slots: {
+           root: 'rounded-xl',
+         }
+       }
      },
-     uiPro: {
-       pageCard: {
-         slots: {
-           root: 'rounded-xl',
-         }
-       }
-     }
    })
  ]
})
  1. 在CSS中将@nuxt/ui-pro替换为@nuxt/ui:
app/assets/css/main.css
@import "tailwindcss";
- @import "@nuxt/ui-pro";
+ @import "@nuxt/ui";
如果您在升级Nuxt UI v4的同时升级到Nuxt 4,请确保更新@source指令以匹配新的目录结构。
app/assets/css/main.css
@import "tailwindcss";
@import "@nuxt/ui";

- @source "../../content/**/*";
+ @source "../../../content/**/*";
src/assets/css/main.css
@import "tailwindcss";
- @import "@nuxt/ui-pro";
+ @import "@nuxt/ui";
  1. 在导入中将@nuxt/ui-pro替换为@nuxt/ui:
- import type { BannerProps } from '@nuxt/ui-pro'
+ import type { BannerProps } from '@nuxt/ui'

来自Nuxt UI

  1. 从Nuxt UI v3升级时,您只需更新到v4:
pnpm add @nuxt/ui tailwindcss

v3的更改

升级到Nuxt UI v4后,请注意以下重要更改:

重命名的按钮组

ButtonGroup组件已重命名为FieldGroup:

<template>
- <UButtonGroup>
+ <UFieldGroup>
    <UButton label="Button" />
    <UButton icon="i-lucide-chevron-down" />
+ </UFieldGroup>
- </UButtonGroup>
</template>

重命名的PageMarquee

PageMarquee组件已重命名为Marquee:

<template>
- <UPageMarquee :items="items" />
+ <UMarquee :items="items" />
</template>

删除PageAccordion

PageAccordion组件已被删除,以支持Accordion:

<template>
- <UPageAccordion
+ <UAccordion
    :items="items"
+   :unmount-on-hide="false"
+   :ui="{ trigger: 'text-base', body: 'text-base text-muted' }"
  />
</template>
PageAccordion组件是一个包装器,它将unmount-on-hide设置为false并自定义ui属性。

重命名模型修改器

Input、InputNumber和Textarea使用的modelModifiers形状在v4中发生了变化:

  1. nullify修饰符被重命名为nullable(它将空值转换为null)。
  2. 添加了一个新的optional修饰符(它将空值转换为undefined)。
- <UInput v-model.nullify="value" />
+ <UInput v-model.nullable="value" />
- <UTextarea v-model="value" :model-modifiers="{ nullify: true }" />
+ <UTextarea v-model="value" :model-modifiers="{ nullable: true }" />

当你想要空值时使用nullable作为null,当你喜欢undefined作为缺失值时使用optional。

表单组件的更改

Form组件在v4中得到了改进,具有更好的状态管理和嵌套表单处理。以下是您需要注意的关键更改:

  1. 模式转换将仅应用于@submit data**,并且不再改变表单的状态。这提供了更好的可预测性,并防止意外的状态变化。
  2. 嵌套表单必须使用nested prop显式启用。这使组件行为更加显式,并防止意外创建嵌套表单。
  3. **嵌套窗体现在应该提供name**属性(类似于UFormField),并将自动从父窗体继承其状态。
<template>
  <UForm :state="state" :schema="schema" @submit="onSubmit">
    <UFormField label="Customer" name="customer">
      <UInput v-model="state.customer" placeholder="Wonka Industries" />
    </UFormField>

    <div v-for="(item, index) in state.items" :key="index">
      <UForm
-       :state="item"
+       :name="`items.${index}`"
        :schema="itemSchema"
+       nested
      >
        <UFormField :label="!index ? 'Description' : undefined" name="description">
          <UInput v-model="item.description" />
        </UFormField>
        <UFormField :label="!index ? 'Price' : undefined" name="price">
          <UInput v-model="item.price" type="number" />
        </UFormField>
      </UForm>
    </div>
  </UForm>
</template>

已删除弃用的实用程序

一些以前在Nuxt UI Pro中可用的Nuxt内容实用程序在v4中已被removed:

  • findPageBreadcrumb
  • findPageHeadline

这些现在完全由Nuxt Content提供。请确保相应地更新您的导入和使用。

- import { findPageHeadline } from '@nuxt/ui-pro/utils/content'
+ import { findPageHeadline } from '@nuxt/content/utils'

- import { findPageBreadcrumb } from '@nuxt/ui-pro/utils/content'
+ import { findPageBreadcrumb } from '@nuxt/content/utils'

AI SDK迁移(可选)

本节仅适用于使用AI SDK和聊天组件(ChatMessage,ChatMessages,ChatPrompt,ChatPromptSubmit,ChatPalette)的情况。如果您不使用AI功能,可以跳过本节。

  1. 更新package.json中的@ai-sdk/vue和ai依赖项,并添加Comark(替换@nuxtjs/mdc以将AI响应渲染为流式Markdown):
{
  "dependencies": {
-   "@ai-sdk/vue": "^1.2.x",
+   "@ai-sdk/vue": "^4.0.x",
-   "ai": "^4.3.x",
+   "ai": "^7.0.x",
+   "@comark/nuxt": "latest"
  }
}

将@comark/nuxt添加到您的模块:

nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxt/ui',
+   '@comark/nuxt'
  ]
})
{
  "dependencies": {
-   "@ai-sdk/vue": "^1.2.x",
+   "@ai-sdk/vue": "^4.0.x",
-   "ai": "^4.3.x",
+   "ai": "^7.0.x",
+   "@comark/vue": "latest"
  }
}
  1. useChat可组合API已更改:input和handleSubmit已被删除,以支持sendMessage,messages现在是您直接设置的引用:
<script setup lang="ts">
  import { useChat } from '@ai-sdk/vue'

- const { messages, input, handleSubmit, status, error, reload, setMessages } = useChat()
+ const { messages, status, error, sendMessage, regenerate, stop } = useChat()
+ const input = ref('')
+
+ function handleSubmit() {
+   sendMessage({ text: input.value })
+   input.value = ''
+ }
</script>
  1. Messages现在使用parts而不是content:
// When manually creating messages
- setMessages([{
+ messages.value = [{
  id: '1',
  role: 'user',
- content: 'Hello world'
+ parts: [{ type: 'text', text: 'Hello world' }]
- }])
+ }]

// In templates
<template>
- <UChatMessage :content="message.content" />
+ <UChatMessage :parts="message.parts" />
</template>
  1. 某些方法已被重命名:
// Regenerate the last message
- reload()
+ regenerate()
  1. 将<MDC>替换为<Markdown>以实现基于部件的渲染。Markdown组件专为流式Markdown而构建,它会在令牌从AI到达时增量渲染令牌。使用AI SDK帮助程序和@nuxt/ui/utils/ai中的isPartStreaming实用程序。
使用shiki插件时,将以下CSS添加到样式表以支持暗模式:
main.css
html.dark .shiki span {
  color: var(--shiki-dark) !important;
  background-color: var(--shiki-dark-bg) !important;
  font-style: var(--shiki-dark-font-style) !important;
  font-weight: var(--shiki-dark-font-weight) !important;
  text-decoration: var(--shiki-dark-text-decoration) !important;
}
<script setup lang="ts">
import { isReasoningUIPart, isTextUIPart } from 'ai'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import shiki from '@comark/nuxt/plugins/shiki'

const plugins = [shiki()]
</script>

<template>
  <UChatMessages :messages="messages" :status="status">
    <template #content="{ message }">
      <template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
        <UChatReasoning
          v-if="isReasoningUIPart(part)"
          :text="part.text"
          :streaming="isPartStreaming(part)"
        >
          <Markdown
            :value="part.text"
            :streaming="isPartStreaming(part)"
            :plugins="plugins"
            class="*:first:mt-0 *:last:mb-0"
          />
        </UChatReasoning>

        <template v-else-if="isTextUIPart(part)">
          <Markdown
            v-if="message.role === 'assistant'"
            :value="part.text"
            :streaming="isPartStreaming(part)"
            :plugins="plugins"
            class="*:first:mt-0 *:last:mb-0"
          />
          <p v-else-if="message.role === 'user'" class="whitespace-pre-wrap">
            {{ part.text }}
          </p>
        </template>
      </template>
    </template>
  </UChatMessages>
</template>
<script setup lang="ts">
import { isReasoningUIPart, isTextUIPart } from 'ai'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
import { Markdown } from '@comark/vue'
import shiki from '@comark/vue/plugins/shiki'

const plugins = [shiki()]
</script>

<template>
  <UChatMessages :messages="messages" :status="status">
    <template #content="{ message }">
      <template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
        <UChatReasoning
          v-if="isReasoningUIPart(part)"
          :text="part.text"
          :streaming="isPartStreaming(part)"
        >
          <Markdown
            :value="part.text"
            :streaming="isPartStreaming(part)"
            :plugins="plugins"
            class="*:first:mt-0 *:last:mb-0"
          />
        </UChatReasoning>

        <template v-else-if="isTextUIPart(part)">
          <Markdown
            v-if="message.role === 'assistant'"
            :value="part.text"
            :streaming="isPartStreaming(part)"
            :plugins="plugins"
            class="*:first:mt-0 *:last:mb-0"
          />
          <p v-else-if="message.role === 'user'" class="whitespace-pre-wrap">
            {{ part.text }}
          </p>
        </template>
      </template>
    </template>
  </UChatMessages>
</template>
有关AI SDK更改的更多详细信息,请查看官方AI SDK迁移指南。
查看AI SDK v7升级PR中的所有更改,以获取详细的迁移参考。