Form Component
<Form>
组件扩展了 HTML <form>
元素,提供 预取 加载界面、 客户端导航 和 渐进增强 功能。
对于需要更新 URL 搜索参数的表单,它特别有用,因为它减少了实现上述功能所需的样板代码。
基本用法:
- TypeScript
- JavaScript
import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
{/* 提交时,输入值会附加到 URL,例如 /search?query=abc */}
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
import Form from 'next/form'
export default function Search() {
return (
<Form action="/search">
{/* 提交时,输入值会附加到 URL,例如 /search?query=abc */}
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
- TypeScript
- JavaScript
import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
{/* 提交时,输入值会附加到 URL,例如 /search?query=abc */}
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
import Form from 'next/form'
export default function Search() {
return (
<Form action="/search">
{/* 提交时,输入值会附加到 URL,例如 /search?query=abc */}
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
参考
<Form>
组件的行为取决于 action
prop 是传递 string
还是 function
。
- 当
action
是字符串时,<Form>
的行为类似于使用GET
方法的原生 HTML 表单。表单数据会编码为 URL 的搜索参数,提交表单时会导航到指定 URL。此外,Next.js 还会: - 当
action
是函数(服务端函数)时,<Form>
的行为类似于 React 表单,提交表单时执行该函数。
- 当
action
是字符串时,<Form>
的行为类似于使用GET
方法的原生 HTML 表单。表单数据会编码为 URL 的搜索参数,提交表单时会导航到指定 URL。此外,Next.js 还会:- 提交表单时执行客户端导航而不是完整页面重载。这会保留共享 UI 和客户端状态。
action
(字符串)Props
当 action
是字符串时,<Form>
组件支持以下 props:
Prop | 示例 | 类型 | 必填 |
---|---|---|---|
action | action="/search" | string (URL 或相对路径) | 是 |
replace | replace={false} | boolean | - |
scroll | scroll={true} | boolean | - |
action
:提交表单时要导航到的 URL 或路径。- 空字符串
""
会导航到同一路由,但更新搜索参数。
- 空字符串
replace
:替换当前历史状态,而不是向浏览器历史栈推送新状态。默认为false
。scroll
:控制导航期间的滚动行为。默认为true
,这意味着会滚动到新路由的顶部,并在前进和后退导航时保持滚动位置。
当 action
是字符串时,<Form>
组件支持以下 props:
Prop | 示例 | 类型 | 必填 |
---|---|---|---|
action | action="/search" | string (URL 或相对路径) | 是 |
replace | replace={false} | boolean | - |
scroll | scroll={true} | boolean | - |
prefetch | prefetch={true} | boolean | - |
action
:提交表单时要导航到的 URL 或路径。- 空字符串
""
会导航到同一路由,但更新搜索参数。
- 空字符串
replace
:替换当前历史状态,而不是向浏览器历史栈推送新状态。默认为false
。scroll
:控制导航期间的滚动行为。默认为true
,这意味着会滚动到新路由的顶部,并在前进和后退导航时保持滚动位置。prefetch
:控制当表单在用户视口中变为可见时是否应该预取路径。默认为true
。
action
(函数)Props
当 action
是函数时,<Form>
组件支持以下 prop:
Prop | 示例 | 类型 | 必填 |
---|---|---|---|
action | action={myAction} | function (服务端函数) | 是 |
action
:提交表单时要调用的服务端函数。详见 React 文档。
提示: 当
action
是函数时,replace
和scroll
props 会被忽略。
注意事项
formAction
:可用于<button>
或<input type="submit">
字段来覆盖action
prop。Next.js 会执行客户端导航,但这种方法不支持预取。- 使用
basePath
时,必须在formAction
路径中也包含它。例如formAction="/base-path/search"
。
- 使用
key
:不支持向字符串action
传递key
prop。如果你想触发重新渲染或执行变更,考虑使用函数action
。
onSubmit
:可用于处理表单提交逻辑。但是,调用event.preventDefault()
会覆盖<Form>
的行为,如导航到指定 URL。method
、encType
、target
:不支持,因为它们会覆盖<Form>
的行为。- 类似地,
formMethod
、formEncType
和formTarget
可用于分别覆盖method
、encType
和target
props,使用它们会回退到原生浏览器行为。 - 如果需要使用这些 props,请使用 HTML
<form>
元素。
- 类似地,
<input type="file">
:当action
是字符串时使用此输入类型,会匹配浏览器行为,提交文件名而不是文件对象。
示例
引导到搜索结果页面的搜索表单
你可以通过将路径作为 action
传递来创建导航到搜索结果页面的搜索表单:
- TypeScript
- JavaScript
import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
import Form from 'next/form'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<button type="submit">Submit</button>
</Form>
)
}
当用户更新查询输入字段并提交表单时,表单数据会编码为 URL 的搜索参数,例如 /search?query=abc
。
提示: 如果向
action
传递空字符串""
,表单会导航到同一路由,但更新搜索参数。
在结果页面,你可以使用 searchParams
page.js
prop 访问查询,并用它从外部源获取数据。
- TypeScript
- JavaScript
import { getSearchResults } from '@/lib/search'
export default async function SearchPage({
searchParams,
}: {
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}) {
const results = await getSearchResults((await searchParams).query)
return <div>...</div>
}
import { getSearchResults } from '@/lib/search'
export default async function SearchPage({ searchParams }) {
const results = await getSearchResults((await searchParams).query)
return <div>...</div>
}
当 <Form>
在用户视口中变为可见时,/search
页面上的共享 UI(如 layout.js
和 loading.js
)会被预取。提交时,表单会立即导航到新路由,并在获取结果时显示加载界面。你可以使用 loading.js
设计回退 UI:
- TypeScript
- JavaScript
export default function Loading() {
return <div>Loading...</div>
}
export default function Loading() {
return <div>Loading...</div>
}
为了覆盖共享 UI 尚未加载的情况,你可以使用 useFormStatus
向用户显示即时反馈。
首先,创建一个在表单挂起时显示加载状态的组件:
- TypeScript
- JavaScript
'use client'
import { useFormStatus } from 'react-dom'
export default function SearchButton() {
const status = useFormStatus()
return (
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
)
}
'use client'
import { useFormStatus } from 'react-dom'
export default function SearchButton() {
const status = useFormStatus()
return (
<button type="submit">{status.pending ? 'Searching...' : 'Search'}</button>
)
}
然后,更新搜索表单页面以使用 SearchButton
组件:
- TypeScript
import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<SearchButton />
</Form>
)
}
- JavaScript
import Form from 'next/form'
import { SearchButton } from '@/ui/search-button'
export default function Page() {
return (
<Form action="/search">
<input name="query" />
<SearchButton />
</Form>
)
}
使用服务端函数进行变更
你可以通过向 action
prop 传递函数来执行变更。
- TypeScript
- JavaScript
import Form from 'next/form'
import { createPost } from '@/posts/actions'
export default function Page() {
return (
<Form action={createPost}>
<input name="title" />
{/* ... */}
<button type="submit">Create Post</button>
</Form>
)
}
import Form from 'next/form'
import { createPost } from '@/posts/actions'
export default function Page() {
return (
<Form action={createPost}>
<input name="title" />
{/* ... */}
<button type="submit">Create Post</button>
</Form>
)
}
变更后,通常会重定向到新资源。你可以使用 next/navigation
中的 redirect
函数导航到新帖子页面。
提示: 由于表单提交的"目标"在执行操作之前是未知的,
<Form>
无法自动预取共享 UI。
- TypeScript
- JavaScript
'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
// 创建新帖子
// ...
// 重定向到新帖子
redirect(`/posts/${data.id}`)
}
'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData) {
// 创建新帖子
// ...
// 重定向到新帖子
redirect(`/posts/${data.id}`)
}
然后,在新页面中,你可以使用 params
prop 获取数据:
- TypeScript
- JavaScript
import { getPost } from '@/posts/data'
export default async function PostPage({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
const data = await getPost(id)
return (
<div>
<h1>{data.title}</h1>
{/* ... */}
</div>
)
}
import { getPost } from '@/posts/data'
export default async function PostPage({ params }) {
const { id } = await params
const data = await getPost(id)
return (
<div>
<h1>{data.title}</h1>
{/* ... */}
</div>
)
}
更多示例请参阅 服务端函数 文档。