形式和突变
表单使你能够在 Web 应用中创建和更新数据。Next.js 提供了一种使用 API 路由处理表单提交和数据突变的强大方法。
¥Forms enable you to create and update data in web applications. Next.js provides a powerful way to handle form submissions and data mutations using API Routes.
很高兴知道:
¥Good to know:
我们很快将推荐 逐步采用 App Router 并使用 服务器操作 来处理表单提交和数据突变。服务器操作允许你定义可以直接从组件调用的异步服务器函数,而无需手动创建 API 路由。
¥We will soon recommend incrementally adopting the App Router and using Server Actions for handling form submissions and data mutations. Server Actions allow you to define asynchronous server functions that can be called directly from your components, without needing to manually create an API Route.
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.
示例
¥Examples
仅服务器形式
¥Server-only form
使用页面路由,你需要手动创建 API 端点来安全地处理服务器上的修改数据。
¥With the Pages Router, you need to manually create API endpoints to handle securely mutating data on the server.
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 })
}
export default function handler(req, res) {
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:
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>
)
}
export default function Page() {
async function onSubmit(event) {
event.preventDefault()
const formData = new FormData(event.target)
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>
)
}
表单验证
¥Form validation
我们建议使用 required
和 type="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:
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)
// ...
}
import { z } from 'zod'
const schema = z.object({
// ...
})
export default async function handler(req, res) {
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:
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>
)
}
import React, { useState } from 'react'
export default function Page() {
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
async function onSubmit(event) {
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:
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>
)
}
import React, { useState } from 'react'
export default function Page() {
const [isLoading, setIsLoading] = useState(false)
async function onSubmit(event) {
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:
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const id = await addPost()
res.redirect(307, `/post/${id}`)
}
export default async function handler(req, res) {
const id = await addPost()
res.redirect(307, `/post/${id}`)
}
设置 cookie
¥Setting cookies
你可以使用响应上的 setHeader
方法在 API 路由内设置 cookie:
¥You can set cookies inside an API Route using the setHeader
method on the response:
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
res.status(200).send('Cookie has been set.')
}
export default async function handler(req, res) {
res.setHeader('Set-Cookie', 'username=lee; Path=/; HttpOnly')
res.status(200).send('Cookie has been set.')
}
读取 cookies
¥Reading cookies
你可以使用 cookies
请求辅助程序读取 API 路由内的 cookie:
¥You can read cookies inside an API Route using the cookies
request helper:
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const auth = req.cookies.authorization
// ...
}
export default async function handler(req, res) {
const auth = req.cookies.authorization
// ...
}
删除 cookies
¥Deleting cookies
你可以使用响应中的 setHeader
方法删除 API 路由内的 cookie:
¥You can delete cookies inside an API Route using the setHeader
method on the response:
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
res.status(200).send('Cookie has been deleted.')
}
export default async function handler(req, res) {
res.setHeader('Set-Cookie', 'username=; Path=/; HttpOnly; Max-Age=0')
res.status(200).send('Cookie has been deleted.')
}