Menu

<Form>

<Form> 组件扩展了 HTML 的 <form> 元素,提供了预获取加载 UI客户端导航提交以及渐进式增强功能。

对于需要更新 URL 搜索参数的表单来说,它很有用,因为它减少了实现上述功能所需的样板代码。

基本用法:

/app/ui/search.js
TypeScript
import Form from 'next/form'
 
export default function Page() {
  return (
    <Form action="/search">
      {/* 提交时,输入值将附加到 URL 中,
          例如:/search?query=abc */}
      <input name="query" />
      <button type="submit">提交</button>
    </Form>
  )
}

参考

<Form> 组件的行为取决于 action 属性是传入字符串还是函数。

  • action字符串时,<Form> 的行为类似于使用 GET 方法的原生 HTML 表单。表单数据会被编码到 URL 的搜索参数中,当表单提交时,会导航到指定的 URL。此外,Next.js 还会:
    • 当表单变得可见时,会预获取路径,这会预加载共享 UI (例如 layout.jsloading.js),从而实现更快的导航。
    • 在表单提交时执行客户端导航而不是完整的页面刷新。这样可以保留共享 UI 和客户端状态。
  • action函数 (服务端操作) 时,<Form> 的行为类似于 React 表单,在表单提交时执行该操作。

action (字符串) 属性

action 是字符串时,<Form> 组件支持以下属性:

属性示例类型是否必需
actionaction="/search"string (URL 或相对路径)
replacereplace={false}boolean-
scrollscroll={true}boolean-
prefetchprefetch={true}boolean-
  • action:表单提交时要导航到的 URL 或路径。
    • 空字符串 "" 将导航到同一路由,但会更新搜索参数。
  • replace:替换浏览器历史记录栈中的当前状态,而不是添加新的状态。默认为 false
  • scroll:控制导航期间的滚动行为。默认为 true,这意味着它会滚动到新路由的顶部,并在前进和后退导航时保持滚动位置。
  • prefetch:控制当表单在用户视口中可见时是否应该预获取路径。默认为 true

action (函数) 属性

action 是函数时,<Form> 组件支持以下属性:

属性示例类型是否必需
actionaction={myAction}function (服务端操作)
  • action:表单提交时要调用的服务端操作。更多信息请参阅 React 文档

值得注意的是:当 action 是函数时,replacescroll 属性会被忽略。

注意事项

  • formAction:可以在 <button><input type="submit"> 字段中使用,以覆盖 action 属性。Next.js 将执行客户端导航,但是这种方法不支持预获取。
    • 当使用 basePath 时,你还必须在 formAction 路径中包含它。例如 formAction="/base-path/search"
  • key:不支持向字符串类型的 action 传递 key 属性。如果你想触发重新渲染或执行变更,可以考虑使用函数类型的 action
  • onSubmit:可以用来处理表单提交逻辑。但是,调用 event.preventDefault() 会覆盖 <Form> 的行为,比如导航到指定的 URL。
  • methodencTypetarget:不支持这些属性,因为它们会覆盖 <Form> 的行为。
    • 同样,formMethodformEncTypeformTarget 可以用来覆盖 methodencTypetarget 属性,使用它们将回退到浏览器原生行为。
    • 如果你需要使用这些属性,请使用 HTML 的 <form> 元素。
  • <input type="file">:当 action 是字符串时使用这种输入类型,将匹配浏览器的行为,提交文件名而不是文件对象。

示例

导向搜索结果页面的搜索表单

你可以通过将路径作为 action 传递来创建一个导航到搜索结果页面的搜索表单:

/app/page.tsx
TypeScript
import Form from 'next/form'
 
export default function Page() {
  return (
    <Form action="/search">
      <input name="query" />
      <button type="submit">提交</button>
    </Form>
  )
}

当用户更新查询输入字段并提交表单时,表单数据将被编码到 URL 的搜索参数中,例如 /search?query=abc

值得注意的是:如果你向 action 传递一个空字符串 "",表单将导航到同一路由,但会更新搜索参数。

在结果页面中,你可以使用 searchParams page.js 属性访问查询,并用它从外部源获取数据。

/app/search/page.tsx
TypeScript
import { getSearchResults } from '@/lib/search'
 
export default async function SearchPage({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined }
}) {
  const results = await getSearchResults(searchParams.query)
 
  return <div>...</div>
}

<Form> 在用户视口中可见时,/search 页面上的共享 UI (如 layout.jsloading.js) 将被预获取。提交时,表单将立即导航到新路由,并在获取结果时显示加载 UI。你可以使用 loading.js 设计加载状态的 UI:

/app/search/loading.tsx
TypeScript
export default function Loading() {
  return <div>加载中...</div>
}

为了处理共享 UI 尚未加载的情况,你可以使用 useFormStatus 向用户显示即时反馈。

首先,创建一个在表单处于待处理状态时显示加载状态的组件:

/app/ui/search-button.tsx
TypeScript
'use client'
import { useFormStatus } from 'react-dom'
 
export default function SearchButton() {
  const status = useFormStatus()
  return (
    <button type="submit">{status.pending ? '搜索中...' : '搜索'}</button>
  )
}

然后,更新搜索表单页面以使用 SearchButton 组件:

/app/page.tsx
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>
  )
}

使用服务端操作进行数据变更

你可以通过向 action 属性传递函数来执行数据变更。

继续翻译前面的内容:

/app/posts/create/page.tsx
TypeScript
import Form from 'next/form'
import { createPost } from '@/posts/actions'
 
export default function Page() {
  return (
    <Form action={createPost}>
      <input name="title" />
      {/* ... */}
      <button type="submit">创建文章</button>
    </Form>
  )
}

在数据变更后,通常需要重定向到新的资源。你可以使用来自 next/navigationredirect 函数导航到新的文章页面。

值得注意的是:由于表单提交的"目的地"要等到操作执行后才能知道,所以 <Form> 无法自动预获取共享 UI。

/app/posts/actions.ts
TypeScript
'use server'
import { redirect } from 'next/navigation'
 
export async function createPost(formData: FormData) {
  // 创建新文章
  // ...
 
  // 重定向到新文章
  redirect(`/posts/${data.id}`)
}

然后,在新页面中,你可以使用 params 属性获取数据:

/app/posts/[id]/page.tsx
TypeScript
import { getPost } from '@/posts/data'
 
export default async function PostPage({ params }: { params: { id: string } }) {
  const data = await getPost(params.id)
 
  return (
    <div>
      <h1>{data.title}</h1>
      {/* ... */}
    </div>
  )
}

更多示例请参阅服务端操作文档。