Skip to content

表单

¥Forms

表单使你能够在 Web 应用中创建和更新数据。Next.js 提供了一种使用 API 路由处理数据突变的强大方法。本指南将引导你了解如何在服务器上处理表单提交。

¥Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle data mutations using API Routes. This guide will walk you through how to handle form submission on the server.

服务器表单

¥Server Forms

要在服务器上处理表单提交,请创建一个 API 端点来安全地更改数据。

¥To handle form submissions on the server, create an API endpoint securely mutate data.

ts
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const data = req.body
  const id = await createItem(data)
  res.status(200).json({ id })
}

然后,使用事件处理程序从客户端调用 API 路由:

¥Then, call the API Route from the client with an event handler:

tsx
import { FormEvent } from 'react'

export default function Page() {
  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()

    const formData = new FormData(event.currentTarget)
    const response = await fetch('/api/submit', {
      method: 'POST',
      body: formData,
    })

    // Handle response if necessary
    const data = await response.json()
    // ...
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit">Submit</button>
    </form>
  )
}

需要了解:

¥Good to know:

  • API 路由 不指定 CORS 标头,这意味着它们仅在默认情况下是同源的。

    ¥API Routes do not specify CORS headers, meaning they are same-origin only by default.

  • 由于 API 路由在服务器上运行,因此我们能够通过 环境变量 使用敏感值(例如 API 密钥),而无需将它们暴露给客户端。这对于应用的安全至关重要。

    ¥Since API Routes run on the server, we're able to use sensitive values (like API keys) through Environment Variables without exposing them to the client. This is critical for the security of your application.

表单验证

¥Form validation

我们建议使用 requiredtype="email" 等 HTML 验证来进行基本的客户端表单验证。

¥We recommend using HTML validation like required and type="email" for basic client-side form validation.

对于更高级的服务器端验证,你可以使用模式验证库(如 zod)在更改数据之前验证表单字段:

¥For more advanced server-side validation, you can use a schema validation library like zod to validate the form fields before mutating the data:

ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { z } from 'zod'

const schema = z.object({
  // ...
})

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const parsed = schema.parse(req.body)
  // ...
}

错误处理

¥Error handling

当表单提交失败时,你可以使用 React state 显示错误消息:

¥You can use React state to show an error message when a form submission fails:

tsx
import React, { useState, FormEvent } from 'react'

export default function Page() {
  const [isLoading, setIsLoading] = useState<boolean>(false)
  const [error, setError] = useState<string | null>(null)

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setIsLoading(true)
    setError(null) // Clear previous errors when a new request starts

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      if (!response.ok) {
        throw new Error('Failed to submit the data. Please try again.')
      }

      // Handle response if necessary
      const data = await response.json()
      // ...
    } catch (error) {
      // Capture the error message to display to the user
      setError(error.message)
      console.error(error)
    } finally {
      setIsLoading(false)
    }
  }

  return (
    <div>
      {error && <div style={{ color: 'red' }}>{error}</div>}
      <form onSubmit={onSubmit}>
        <input type="text" name="name" />
        <button type="submit" disabled={isLoading}>
          {isLoading ? 'Loading...' : 'Submit'}
        </button>
      </form>
    </div>
  )
}

显示加载状态

¥Displaying loading state

当表单在服务器上提交时,你可以使用 React state 显示加载状态:

¥You can use React state to show a loading state when a form is submitting on the server:

tsx
import React, { useState, FormEvent } from 'react'

export default function Page() {
  const [isLoading, setIsLoading] = useState<boolean>(false)

  async function onSubmit(event: FormEvent<HTMLFormElement>) {
    event.preventDefault()
    setIsLoading(true) // Set loading to true when the request starts

    try {
      const formData = new FormData(event.currentTarget)
      const response = await fetch('/api/submit', {
        method: 'POST',
        body: formData,
      })

      // Handle response if necessary
      const data = await response.json()
      // ...
    } catch (error) {
      // Handle error if necessary
      console.error(error)
    } finally {
      setIsLoading(false) // Set loading to false when the request completes
    }
  }

  return (
    <form onSubmit={onSubmit}>
      <input type="text" name="name" />
      <button type="submit" disabled={isLoading}>
        {isLoading ? 'Loading...' : 'Submit'}
      </button>
    </form>
  )
}

重定向

¥Redirecting

如果你想在更改后将用户重定向到不同的路由,你可以 redirect 到任何绝对或相对 URL:

¥If you would like to redirect the user to a different route after a mutation, you can redirect to any absolute or relative URL:

ts
import type { NextApiRequest, NextApiResponse } from 'next'

export default async function handler(
  req: NextApiRequest,
  res: NextApiResponse
) {
  const id = await addPost()
  res.redirect(307, `/post/${id}`)
}