迁移到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
- 将
package.json中的@nuxt/ui-pro替换为@nuxt/ui:
pnpm remove @nuxt/ui-pro
pnpm add @nuxt/ui tailwindcss
yarn remove @nuxt/ui-pro
yarn add @nuxt/ui tailwindcss
npm uninstall @nuxt/ui-pro
npm install @nuxt/ui tailwindcss
bun remove @nuxt/ui-pro
bun add @nuxt/ui tailwindcss
- 在
nuxt.config.ts中将@nuxt/ui-pro替换为@nuxt/ui:
nuxt.config.ts
export default defineNuxtConfig({
modules: [
- '@nuxt/ui-pro',
+ '@nuxt/ui'
]
})
- 在
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'
}
}
})
]
})
- 在
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',
- }
- }
- }
})
- 在
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',
- }
- }
- }
})
]
})
- 在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";
- 在导入中将
@nuxt/ui-pro替换为@nuxt/ui:
- import type { BannerProps } from '@nuxt/ui-pro'
+ import type { BannerProps } from '@nuxt/ui'
来自Nuxt UI
- 从Nuxt UI v3升级时,您只需更新到v4:
pnpm add @nuxt/ui tailwindcss
yarn add @nuxt/ui tailwindcss
npm install @nuxt/ui tailwindcss
bun 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中发生了变化:
nullify修饰符被重命名为nullable(它将空值转换为null)。- 添加了一个新的
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中得到了改进,具有更好的状态管理和嵌套表单处理。以下是您需要注意的关键更改:
- 模式转换将仅应用于
@submitdata**,并且不再改变表单的状态。这提供了更好的可预测性,并防止意外的状态变化。 - 嵌套表单必须使用
nestedprop显式启用。这使组件行为更加显式,并防止意外创建嵌套表单。 - **嵌套窗体现在应该提供
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:
findPageBreadcrumbfindPageHeadline
这些现在完全由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功能,可以跳过本节。
- 更新
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"
}
}
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>
- 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>
- 某些方法已被重命名:
// Regenerate the last message
- reload()
+ regenerate()
- 将
<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>