Next.js 学习笔记(四)——数据获取

数据获取

数据获取、缓存和重新验证

数据获取是任何应用程序的核心部分。本页介绍如何在 React 和 Next.js 中获取、缓存和重新验证数据。

有四种方法可以获取数据:

  1. 在服务器上,使用 fetch
  2. 在服务器上,使用第三方库
  3. 在客户端上,通过路由处理程序
  4. 在客户单上,使用第三方库

在服务器上使用 fetch 获取数据

Next.js 扩展了原生的 fetch Web API,允许你为服务器上的每个 fetch 请求配置缓存和重新验证行为。React 扩展了 fetch,以便在渲染 React 组件树时自动存储 fetch 请求。

在服务器组件、路由处理程序和服务器操作中,可以将 fetchasync/await 一起使用。

例如:

// app/page.tsxasync function getData() {const res = await fetch('https://api.example.com/...')// 返回值是 *not* 序列化的// 你可以返回 Date、Map、Set等if (!res.ok) {// 这将激活最接近的 `error.js` 错误边界throw new Error('Failed to fetch data')}return res.json()
}export default async function Page() {const data = await getData()return <main></main>
}

需要知道

  • Next.js 提供了在服务器组件(如:cookiesheaders)中获取数据时可能需要的有用功能。这将导致路由被动态渲染,因为它们依赖于请求时间信息
  • 在路由处理程序中,由于路由处理程序不是 React 组件树的一部分,所有 fetch 请求不会被存储
  • 要在带有 TypeScript 的服务器组件中使用 async/await,你需要使用 TypeScript 5.1.3 或更高版本的 @types/react 18.2.8 或更高级别
缓存数据

缓存仓库数据,因此不需要在每次请求时都从数据源重新获取数据。

默认情况下,Next.js 会自动将 fetch 的返回值缓存在服务器上的数据缓存中。这意味着数据可以在构建时或请求时提取、缓存,并在每个数据请求中重用。

// 'force-cache' 是默认值,可以省略
fetch('https://...', { cache: 'force-cache' })

使用 POST 方法的 fetch 请求也会自动缓存。除非它在使用 POST 方法的路由处理程序中,否则它不会被缓存。

什么是数据缓存?

数据缓存是一个持久的 HTTP 缓存。根据你的平台,缓存可以自动扩展并在多个区域之间共享。

了解有关数据缓存的更多信息。

重新验证数据

重新验证是清除数据缓存并重新回去数据的过程。当你的数据发送更改并且你希望确保显示最新信息时,这一点非常有用。

缓存数据可以通过两种方式重新验证:

  • 基于时间的重新验证:在经过一定时间后自动重新验证数据。这对于很少更改且新鲜度不那么重要的数据非常有用。
  • 按需重新验证:根据事件手动重新验证数据(例如:表单提交)。按需重新验证可以使用基于标记或基于路径的方法一次重新验证数据组。当你希望确保尽快显示最新数据时(例如:当无头 CMS 的内容更新时),这一点非常有用。
基于时间的重新验证

要按一定时间间隔重新验证数据,可以使用 fetchnext.revalidate 选项设置资源的缓存生存期(以秒为单位)。

fetch('https://...', { next: { revalidate: 3600 } })

或者,要重新验证路由段中的所有 fetch 请求,可以使用段配置选项。

// layout.js | page.jsexport const revalidate = 3600 // 最多每小时重新验证一次

如果在静态渲染的路由中有多个 fetch 请求,并且每个请求都有不同的重新验证频率。最低时间将用于所有请求。对于动态渲染的路由,每个 fetch 请求都将独立地重新验证。

了解有关基于时间的重新验证的更多信息。

按需重新验证

可以按需通过服务器操作或路由处理程序中的路由(revalidatePath)或缓存标记(revalidateTag)重新验证数据。

Next.js 有一个缓存标记系统,用于使跨路由的 fetch 请求无效。

  1. 使用 fetch 时,可以选择使用一个或多个标记标记缓存条目
  2. 然后,你可以调用 revalidateTag 来重新验证与标记相关联的所有条目

例如,以下 fetch 请求会添加缓存标记 collection

// app/page.tsxexport default async function Page() {const res = await fetch('https://...', { next: { tags: ['collection'] } })const data = await res.json()// ...
}

然后,你可以通过在服务器操作中调用 revalidateTag 来重新验证此带有 collection 标记的 fetch 调用:

// app/actions.ts'use server'import { revalidateTag } from 'next/cache'export default async function action() {revalidateTag('collection')
}

了解更多按需重新验证。

错误处理和重新验证

如果在尝试重新验证数据时抛出错误,则将继续从缓存中提供最后一个成功生成的数据。在下一个后续请求中,Next.js 将重试重新验证数据。

选择退出数据缓存

如果出现以下情况,则不会缓存 fetch 请求:

  • cache: 'no-store' 被添加到 fetch 请求中
  • revalidate: 0 选项被添加到各个 fetch 请求中
  • fetch 请求位于使用 POST 方法的路由器处理程序中
  • fetch 请求是在使用 headerscookies 之后发出的
  • 使用了 const dynamic = 'force-dynamic' 路由段选项
  • fetchCache 路由段选项默认配置为跳过缓存
  • fetch 请求使用 AuthorizationCookie 头,并且在组件树中有一个未缓存的请求
单个 fetch 请求

要选择不缓存单个 fetch 请求,可以将 fetch 中的 cache 选项设置为 'no-store'。这将在每次请求时动态获取数据。

// layout.js | page.jsfetch('https://...', { cache: 'no-store' })

查看 fetch API 引用中的所有可用缓存选项。

多个 fetch 请求

如果在一个路由段(例如:布局或页面)中有多个 fetch 请求,则可以使用段配置选项配置该段中所有数据请求的缓存行为。

但是,我们建议单独配置每个 fetch 请求的缓存行为。这使你能够对缓存行为进行更精细的控制。

使用第三方库在服务器上获取数据

如果你使用的第三方库不支持或不公开 fetch(例如:数据库、CMS 或 ORM 客户端),则可以使用路由段配置选项和 React 的 cache 功能配置这些请求的缓存和重新验证行为。

是否缓存数据将取决于路由段是静态渲染还是动态渲染。如果段是静态的(默认),则请求的输出将被缓存并作为路由段的一部分重新验证。如果分段是动态的,则不会缓存请求的输出,并且在渲染分段时会在每个请求上重新获取该输出。

你还可以使用实验性的 unstable_cache API。

例子

在以下示例中:

  • React cache 函数用于存储数据请求。

  • layout.tspage.ts 段中,revalidate 选项设置为 3600,这意味着数据将被缓存并最多每小时重新验证一次。

// app/utils.tsimport { cache } from 'react'export const getItem = cache(async (id: string) => {const item = await db.item.findUnique({ id })return item
})

尽管 getItem 函数被调用了两次,但只会对数据库进行一次查询。

// app/item/[id]/layout.tsximport { getItem } from '@/utils/get-item'export const revalidate = 3600 // 最多每小时重新验证一次数据export default async function Layout({params: { id },
}: {params: { id: string }
}) {const item = await getItem(id)// ...
}
// app/item/[id]/page.tsximport { getItem } from '@/utils/get-item'export const revalidate = 3600 // 最多每小时重新验证一次数据export default async function Page({params: { id },
}: {params: { id: string }
}) {const item = await getItem(id)// ...
}

使用路由处理程序在客户端上获取数据

如果需要在客户端组件中获取数据,可以从客户端调用路由处理程序。路由处理程序在服务器上执行,并将数据返回给客户端。当您不想向客户端公开敏感信息(如:API 令牌)时,这很有用。

有关示例,请参阅路由处理程序文档。

服务器组件和路由处理程序

由于服务器组件在服务器上渲染,因此不需要从服务器组件调用路由处理程序来获取数据。相反,您可以直接在服务器组件内部获取数据。

使用第三方库在客户端上获取数据

您还可以使用第三方库(如:SWR 或 React Query)在客户端上获取数据。这些库提供了自己的 API,用于存储请求、缓存、重新验证和更改数据。

未来的 API:

use 是一个 React 函数,它接受并处理函数返回的 promise。目前不建议在客户端组件中使用在 use 中嵌套 fetch,并且可能会触发多次重新渲染。在 React 文档中了解更多有关 use

服务器操作和突变

服务器操作是在服务器上执行的异步函数。它们可以在服务器和客户端组件中用于处理 Next.js 应用程序中的表单提交和数据突变。

通过服务器操作了解有关形式和突变的更多信息→ YouTube(10分钟)

约定

服务器操作可以用 React "use server" 定义指令。你可以将该指令放在 async 函数的顶部,以将该函数标记为服务器操作,也可以放在单独文件的顶部,将该文件的所有导出标记为服务器动作。

服务器组件

服务器组件可以使用内联功能级别或模块级别的 "use server" 指令。要内联服务器操作,请在函数体顶部添加 "use server"

// app/page.tsx// 服务器组件
export default function Page() {// 服务器操作async function create() {'use server'// ...}return (// ...)
}
客户端组件

客户端组件只能导入使用模块级 "use server" 指令的操作。

要在客户端组件中调用服务器操作,请创建一个新文件,并在其顶部添加 "use Server" 指令。文件中的所有函数都将标记为可在客户端组件和服务器组件中重复使用的服务器操作:

// app/actions.ts'use server'export async function create() {// ...
}
// app/ui/button.tsximport { create } from '@/app/actions'export function Button() {return (// ...)
}

你还可以将服务器操作作为 prop 传递给客户端组件:

<ClientComponent updateItem={updateItem} />
// app/client-component.jsx'use client'export default function ClientComponent({ updateItem }) {return <form action={updateItem}>{/* ... */}</form>
}

行为

  • 可以使用 <form> 元素中的 action 属性调用服务器操作:
    • 默认情况下,服务器组件支持渐进式增强,这意味着即使 JavaScript 尚未加载或被禁用,表单也会被提交。
    • 在客户端组件中,如果 JavaScript 尚未加载,调用服务器操作的表单将对提交进行排队,从而优先考虑客户端水合。
    • 水合后,浏览器不会在表单提交时刷新。
  • 服务器操作不限于 <form>,可以从事件处理程序、useEffect、第三方库和其他表单元素(如:<button>)调用。
  • 服务器操作与 Next.js 缓存和重新验证体系结构集成。当调用一个操作时,Next.js 可以在单个服务器往返中返回更新的 UI 和新数据。
  • 在幕后,操作使用 POST 方法,并且只有此 HTTP 方法才能调用它们。
  • 服务器操作的参数和返回值必须可由 React 序列化。有关可序列化参数和值的列表,请参阅 React 文档。
  • 服务器操作是函数。这意味着它们可以在应用程序中的任何位置重复使用。
  • 服务器操作从其使用的页面或布局继承运行时。

例子

表单

React 扩展了 HTML <form> 元素,允许使用 action prop 调用服务器操作。

在表单中调用时,该操作会自动接收 FormData 对象。你不需要使用 React useState 来管理字段,而是可以使用本地 FormData 方法提取数据:

// app/invoices/page.tsxexport default function Page() {async function createInvoice(formData: FormData) {'use server'const rawFormData = {customerId: formData.get('customerId'),amount: formData.get('amount'),status: formData.get('status'),}// 变异数据// 重新验证缓存}return <form action={createInvoice}>...</form>
}

需要知道:

  • 示例:带有加载和错误状态的表单

  • 在处理具有多个字段的表单时,你可能需要考虑将 entries() 方法与 JavaScript 的 Object.fromEntries() 一起使用。例如:const rawFormData = Object.fromEntries(formData.entries())

  • 请参阅 React <form> 文档以了解更多信息。

传递其他参数

你可以使用 JavaScript bind 方法将其他参数传递给服务器操作。

// app/client-component.tsx'use client'import { updateUser } from './actions'export function UserProfile({ userId }: { userId: string }) {const updateUserWithId = updateUser.bind(null, userId)return (<form action={updateUserWithId}><input type="text" name="name" /><button type="submit">Update User Name</button></form>)
}

除了表单数据外,服务器操作还将接收 userId 参数:

// app/actions.js'use server'export async function updateUser(userId, formData) {// ...
}

需要知道:

  • 另一种选择是将参数作为表单中的隐藏输入字段传递(例如:<input type=“hidden” name=“userId” value={userId} />)。但是,该值将是渲染的 HTML 的一部分,不会进行编码。

  • .bind 适用于服务器组件和客户端组件。它还支持渐进增强。

挂起的状态

你可以使用 React useFormStatus hook 来显示提交表单时的挂起状态。

  • useFormStatus 返回特定 <form> 的状态,因此必须将其定义为 <form> 元素的子级
  • useFormStatus 是一个 React hook,因此必须在客户端组件中使用。
// app/submit-button.tsx'use client'import { useFormStatus } from 'react-dom'export function SubmitButton() {const { pending } = useFormStatus()return (<button type="submit" aria-disabled={pending}>Add</button>)
}

<SubmitButton /> 然后可以以任何形式嵌套:

// app/page.tsximport { SubmitButton } from '@/app/submit-button'
import { createItem } from '@/app/actions'// 服务器组件
export default async function Home() {return (<form action={createItem}><input type="text" name="field-name" /><SubmitButton /></form>)
}
服务器端验证和错误处理

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

对于更高级的服务器端验证,可以使用类似 zod 的库要在更改数据之前验证表单字段,请执行以下操作:

// app/actions.ts'use server'import { z } from 'zod'const schema = z.object({email: z.string({invalid_type_error: 'Invalid Email',}),
})export default async function createUser(formData: FormData) {const validatedFields = schema.safeParse({email: formData.get('email'),})// 如果表单数据无效,请提前返回if (!validatedFields.success) {return {errors: validatedFields.error.flatten().fieldErrors,}}// 突变数据
}

一旦在服务器上验证了字段,就可以在操作中返回一个可序列化的对象,并使用 React useFormState hook 向用户显示消息。

  • 通过将操作传递给 useFormState,操作的函数签名将更改为接收新的 prevStateinitialState 参数作为其第一个参数。

  • useFormState 是一个 React 钩子,因此必须在客户端组件中使用。

// app/actions.ts'use server'export async function createUser(prevState: any, formData: FormData) {// ...return {message: 'Please enter a valid email',}
}

然后,你可以将操作传递到 useFormState hook,并使用返回的 state 显示错误消息。

// app/ui/signup.tsx'use client'import { useFormState } from 'react-dom'
import { createUser } from '@/app/actions'const initialState = {message: null,
}export function Signup() {const [state, formAction] = useFormState(createUser, initialState)return (<form action={formAction}><label htmlFor="email">Email</label><input type="text" id="email" name="email" required />{/* ... */}<p aria-live="polite" className="sr-only">{state?.message}</p><button>Sign up</button></form>)
}

需要知道:

  • 在更改数据之前,应始终确保用户也有权执行操作。请参阅身份验证和授权。
乐观地更新

你可以使用 React useOptimistic hook,以便在服务器操作完成之前乐观地更新 UI,而不是等待响应:

// app/page.tsx'use client'import { useOptimistic } from 'react'
import { send } from './actions'type Message = {message: string
}export function Thread({ messages }: { messages: Message[] }) {const [optimisticMessages, addOptimisticMessage] = useOptimistic<Message[]>(messages,(state: Message[], newMessage: string) => [...state,{ message: newMessage },])return (<div>{optimisticMessages.map((m, k) => (<div key={k}>{m.message}</div>))}<formaction={async (formData: FormData) => {const message = formData.get('message')addOptimisticMessage(message)await send(message)}}><input type="text" name="message" /><button type="submit">Send</button></form></div>)
}
嵌套元素

你可以在 <form> 中嵌套的元素中调用服务器操作,如 <button><input type=“submit”><input type=“image”>。这些元素接受 formAction prop 或事件处理程序。

这在你想在一个表单中调用多个服务器操作的情况下很有用。例如,你可以创建一个特定的 <button> 元素,用于保存后草稿并发布它。请参阅 React <form> 文档了解更多信息。

无表单元素

虽然在 <form> 元素中使用服务器操作很常见,但它们也可以从代码的其他部分调用,如:事件处理程序和 useEffect

事件处理程序

你可以从事件处理程序(如:onClick)调用服务器操作。例如,要增加类似计数:

// app/like-button.tsx'use client'import { incrementLike } from './actions'
import { useState } from 'react'export default function LikeButton({ initialLikes }: { initialLikes: number }) {const [likes, setLikes] = useState(initialLikes)return (<><p>Total Likes: {likes}</p><buttononClick={async () => {const updatedLikes = await incrementLike()setLikes(updatedLikes)}}>Like</button></>)
}

为了改善用户体验,我们建议使用其他 React API,如:useOptimistic 并使用 Transition 以在服务器上完成服务器操作执行之前更新 UI,或显示挂起状态。

你还可以将事件处理程序添加到表单元素中,例如,在 onChange上保存表单字段:

// app/ui/edit-post.tsx'use client'import { publishPost, saveDraft } from './actions'export default function EditPost() {return (<form action={publishPost}><textareaname="content"onChange={async (e) => {await saveDraft(e.target.value)}}/><button type="submit">Publish</button></form>)
}

对于这种情况,其中可能会快速连续触发多个事件,我们建议使用防抖来阻止不必要的服务器操作调用。

useEffect

你可以使用 React useEffect hook 在组件装载或依赖项更改时调用服务器操作。这对于依赖于全局事件或需要自动触发的突变很有用。例如,onKeyDown 用于应用程序快捷方式,交叉点观察者挂钩用于无限滚动,或者当组件安装以更新视图计数时:

// app/view-count.tsx'use client'import { incrementViews } from './actions'
import { useState, useEffect } from 'react'export default function ViewCount({ initialViews }: { initialViews: number }) {const [views, setViews] = useState(initialViews)useEffect(() => {const updateViews = async () => {const updatedViews = await incrementViews()setViews(updatedViews)}updateViews()}, [])return <p>Total Views: {views}</p>
}

记住要考虑 useEffect 的行为和注意事项。

错误处理

当抛出错误时,它将被客户端上最近的 error.js<Suspense> 边界捕获。我们建议使用 try/catch 返回要由 UI 处理的错误。

例如,你的服务器操作可能会通过返回消息来处理创建新项目时的错误:

// app/actions.ts'use server'export async function createTodo(prevState: any, formData: FormData) {try {// 突变数据} catch (e) {throw new Error('Failed to create task')}
}

需要知道:

  • 除了抛出错误之外,你还可以返回一个由 useFormStatus 处理的对象。请参阅服务器端验证和错误处理。
重新验证数据

你可以使用 revalidatePath API 重新验证服务器操作中的 Next.js 缓存:

// app/actions.ts'use server'import { revalidatePath } from 'next/cache'export async function createPost() {try {// ...} catch (error) {// ...}revalidatePath('/posts')
}

或者使用 revalidateTag 使具有缓存标记的特定数据提取无效:

// app/actions.ts'use server'import { revalidateTag } from 'next/cache'export async function createPost() {try {// ...} catch (error) {// ...}revalidateTag('posts')
}
重定向

如果你希望在完成服务器操作后将用户重定向到不同的路由,则可以使用 redirect API。redirect 需要在 try/catch 块之外调用:

// app/actions.ts'use server'import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'export async function createPost(id: string) {try {// ...} catch (error) {// ...}revalidateTag('posts') // 更新缓存的帖子redirect(`/post/${id}`) // 导航到新的文章页面
}
Cookies

你可以使用 cookies API 中的 getsetdelete 服务器操作中的 cookies:

// app/actions.ts'use server'import { cookies } from 'next/headers'export async function exampleAction() {// Get cookieconst value = cookies().get('name')?.value// Set cookiecookies().set('name', 'Delba')// Delete cookiecookies().delete('name')
}

请参阅有关从服务器操作中删除 cookie 的其他示例。

安全

认证与授权

你应该像对待公开的 API 端点一样对待服务器操作,并确保用户有权执行该操作。例如:

// app/actions.ts'use server'import { auth } from './lib'export function addItem() {const { user } = auth()if (!user) {throw new Error('You must be signed in to perform this action')}// ...
}
闭包和加密

在组件内定义服务器操作会创建一个闭包,其中操作可以访问外部函数的范围。例如,publish 操作可以访问 publishVersion 变量:

// app/page.tsxexport default function Page() {const publishVersion = await getLatestVersion();async function publish(formData: FormData) {"use server";if (publishVersion !== await getLatestVersion()) {throw new Error('The version has changed since pressing publish');}...}return <button action={publish}>Publish</button>;
}

当你需要在渲染时捕获数据快照(例如:publishVersion),以便稍后调用操作时使用时,闭包非常有用。

然而,为了实现这一点,在调用操作时,捕获的变量会被发送到客户端并返回到服务器。为了防止敏感数据暴露给客户端,Next.js 自动对封闭变量进行加密。每次构建 Next.js 应用程序时,都会为每个操作生成一个新的私钥。这意味着只能对特定的生成调用操作。

需要知道:

  • 我们不建议仅依靠加密来防止敏感值在客户端上暴露。相反,你应该使用 React taint API 来主动防止特定数据发送到客户端。
重写加密密钥(高级)

当跨多个服务器自托管 Next.js 应用程序时,每个服务器实例最终可能会使用不同的加密密钥,从而导致潜在的不一致性。

为了缓解这种情况,可以使用 process.env.NEXT_SERVER_ACTIONS_encryption_key 环境变量覆盖加密密钥。指定此变量可确保加密密钥在构建中是持久的,并且所有服务器实例都使用相同的密钥。

这是一个高级用例,其中跨多个部署的一致加密行为对您的应用程序至关重要。您应该考虑标准的安全实践,如密钥轮换和签名。

需要知道:

  • 部署到 Vercel 的 Next.js 应用程序会自动处理此问题。
允许的来源(高级)

由于服务器操作可以在 <form> 元素中调用,这会使它们受到 CSRF 攻击。

在后台,服务器操作使用 POST 方法,并且只允许此 HTTP 方法调用它们。这可以防止现代浏览器中的大多数 CSRF 漏洞,尤其是SameSite cookie 是默认的。

作为一种额外的保护,Next.js 中的 Server Actions 还比较了 Origin 头到 Host 头(或 X-Forwarded-Host)。如果这些不匹配,请求将被中止。换句话说,服务器操作只能在承载它的页面所在的主机上调用。

对于使用反向代理或多层后端架构的大型应用程序(其中服务器 API 与生产域不同),建议使用配置选项serverActions.allowedOrigins 选项来指定安全来源列表。该选项接受一个字符串数组。

// next.config.js/** @type {import('next').NextConfig} */
module.exports = {experimental: {serverActions: {allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],},},
}

了解有关安全和服务器操作的详细信息。

额外资源

有关服务器操作的更多信息,请查看以下 React 文档:

  • "use server"
  • <form>
  • useFormStatus
  • useFormState
  • useOptimistic

数据获取模式和最佳实践

React 和 Next.js 中有一些获取数据的推荐模式和最佳实践。本页将介绍一些最常见的模式以及如何使用它们。

在服务器上获取数据

只要可能,我们建议在服务器上获取数据。这允许你:

  • 可以直接访问后端数据资源(如:数据库)。

  • 通过防止敏感信息(如:访问令牌和 API 密钥)暴露给客户端,使你的应用程序更加安全。

  • 在同一环境中获取数据并进行渲染。这既减少了客户端和服务器之间的来回通信,也减少了客户端上主线程的工作。

  • 使用单个往返而不是在客户端上执行多个单独的请求来执行多个数据提取。

  • 减少客户端-服务器瀑布。

  • 根据你所在的地区,数据获取也可以在离数据源更近的地方进行,从而减少延迟并提高性能。

你可以使用服务器组件、路由处理程序和服务器操作在服务器上获取数据。

在需要的地方获取数据

如果你需要在树中的多个组件中使用相同的数据(例如:当前用户),则不必全局获取数据,也不必在组件之间转发 props。相反,你可以在需要数据的组件中使用 fetch 或 React cache,而不用担心对同一数据发出多个请求的性能影响。

这是可能的,因为 fetch 请求是自动存储的。了解有关请求备忘录的更多信息。

需要知道:

  • 这也适用于布局,因为不可能在父布局及其子布局之间传递数据。

Streaming

Streaming 和 Suspense 是 React 的功能,允许你逐步渲染和递增地将 UI 的渲染单元流式传输到客户端。

使用服务器组件和嵌套布局,你可以立即渲染页面中不特别需要数据的部分,并显示页面中正在获取数据的部分的加载状态。这意味着用户不必等待整个页面加载后才能开始与之交互。

在这里插入图片描述

要了解有关 Streaming 和 Suspense 的更多信息,请参阅加载 UI 和 Streaming 与 Suspense 页面。

并行和顺序数据获取

在 React 组件内部获取数据时,需要注意两种数据获取模式:并行(Parallel)和顺序(Sequential)。

在这里插入图片描述

  • 通过顺序数据获取,路由中的请求是相互依赖的,因此会创建瀑布。在某些情况下,你可能需要此模式,因为一次提取取决于另一次提取的结果,或者您希望在下一次提取之前满足一个条件以节省资源。然而,这种行为也可能是无意的,并导致更长的加载时间。

  • 通过并行数据获取,路由中的请求被急切地启动,并将同时加载数据。这减少了客户端-服务器瀑布和加载数据所需的总时间。

顺序数据获取

如果你有嵌套的组件,并且每个组件都获取自己的数据,那么如果这些数据请求不同,则数据提取将按顺序进行(这不适用于对相同数据的请求,因为它们会自动存储)。

例如,Playlists 组件只有在 Artist 组件完成获取数据后才会开始获取数据,因为 Playlists 取决于 artistID prop:

// app/artist/[username]/page.tsxasync function Playlists({ artistID }: { artistID: string }) {// 等待播放列表const playlists = await getArtistPlaylists(artistID)return (<ul>{playlists.map((playlist) => (<li key={playlist.id}>{playlist.name}</li>))}</ul>)
}export default async function Page({params: { username },
}: {params: { username: string }
}) {// 等待艺术家const artist = await getArtist(username)return (<><h1>{artist.name}</h1><Suspense fallback={<div>Loading...</div>}><Playlists artistID={artist.id} /></Suspense></>)
}

在这种情况下,你可以使用 loading.js(用于路由段)或 React <Suspense>(用于嵌套组件)来显示即时加载状态,同时 React 在结果中进行流式传输。

这将防止整个路由被数据获取阻塞,并且用户将能够与页面中未被阻塞的部分进行交互。

阻止数据请求:

防止瀑布的另一种方法是在应用程序的根全局获取数据,但这将阻止其下所有路由段的渲染,直到数据完成加载。这可以被描述为 “要么全取,要么全无” 的数据获取。要么你拥有页面或应用程序的全部数据,要么没有。

任何带有 await 的请求获取都将阻止其下整个树的渲染和数据提取,除非它们被封装在 <Suspense> 边界中或使用 loading.js。另一种选择是使用并行数据获取或预加载模式。

并行数据获取

要并行获取数据,你可以通过在使用数据的组件外部定义请求,然后从组件内部调用请求来更早地启动请求。这通过并行启动请求来节省时间,但是,在所有的 promises 都 resolved 之前,用户不会看到渲染的结果。

在下面的示例中,getArtistgetArtistAlbums 函数在 Page 组件外部定义,然后在组件内部调用,我们等待这两个承诺得到解决:

// app/artist/[username]/page.tsximport Albums from './albums'async function getArtist(username: string) {const res = await fetch(`https://api.example.com/artist/${username}`)return res.json()
}async function getArtistAlbums(username: string) {const res = await fetch(`https://api.example.com/artist/${username}/albums`)return res.json()
}export default async function Page({params: { username },
}: {params: { username: string }
}) {// 并行启动多个请求const artistData = getArtist(username)const albumsData = getArtistAlbums(username)// 等待所有的 promises 都 resolveconst [artist, albums] = await Promise.all([artistData, albumsData])return (<><h1>{artist.name}</h1><Albums list={albums}></Albums></>)
}

为了改善用户体验,可以添加 Suspense Boundary 以分解渲染工作并尽快渲染部分结果。

预加载数据

防止瀑布的另一种方法是使用预加载模式。你可以选择创建一个 preload 函数来进一步优化并行数据获取。有了这种方法,你就不必把承诺当作 props。preload 函数也可以有任何名称,因为它是一个模式,而不是 API。

// components/Item.tsximport { getItem } from '@/utils/get-item'export const preload = (id: string) => {// void 计算给定的表达式并返回 undefined// https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/voidvoid getItem(id)
}
export default async function Item({ id }: { id: string }) {const result = await getItem(id)// ...
}
// app/item/[id]/page.tsximport Item, { preload, checkIsAvailable } from '@/components/Item'export default async function Page({params: { id },
}: {params: { id: string }
}) {// 开始加载项数据preload(id)// 执行另一个异步任务const isAvailable = await checkIsAvailable()return isAvailable ? <Item id={id} /> : null
}
使用 React cacheserver-only 和预加载模式

你可以将 cache 功能、preload 模式和 server-only 的包结合起来,创建一个可在整个应用程序中使用的数据获取实用程序。

// utils/get-item.tsimport { cache } from 'react'
import 'server-only'export const preload = (id: string) => {void getItem(id)
}export const getItem = cache(async (id: string) => {// ...
})

使用这种方法,你可以更早地获取数据、缓存响应,并确保这种数据获取只发生在服务器上。

Layouts、Pages 或其他组件可以使用 utils/get-item 导出来控制何时获取项的数据。

需要知道:

  • 我们建议使用 server-only 的包,以确保客户端永远不会使用服务器数据获取功能。

防止敏感数据暴露给客户端

我们建议使用 React 的 taint API,即 taintObjectReferencetaintUniqueValue,以防止整个对象实例或敏感值被传递到客户端。

要在应用程序中启用 tainting,请将 Next.js 配置 experial.taint 选项设置为 true

// next.config.jsmodule.exports = {experimental: {taint: true,},
}

然后将要 taint 的对象或值传递给 experimental_taintObjectReferenceexperimental_taintUniqueValue 函数:

// app/utils.tsimport { queryDataFromDB } from './api'
import {experimental_taintObjectReference,experimental_taintUniqueValue,
} from 'react'export async function getUserData() {const data = await queryDataFromDB()experimental_taintObjectReference('Do not pass the whole user object to the client',data)experimental_taintUniqueValue("Do not pass the user's phone number to the client",data,data.phoneNumber)return data
}
// app/page.tsximport { getUserData } from './data'export async function Page() {const userData = getUserData()return (<ClientComponentuser={userData} // 这将导致一个错误,因为 tainObjectReferencephoneNumber={userData.phoneNumber} // 这将导致一个错误,因为 tainUniqueValue/>)
}

了解有关安全和服务器操作的详细信息。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/233530.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人工智能中不可预测的潜在错误可能是灾难性的——数学解释

一、说明 有没有人研究评估AI的错误产生的后果有多么严重&#xff0c;是否存在AI分险评估机制&#xff1f;更高维度上&#xff0c;人工智能的未来是反乌托邦还是乌托邦&#xff1f;这个问题一直是争论的话题&#xff0c;各大阵营都支持。我相信我们无法准确预测这两种结果。这是…

el-form与el-upload结合上传带附件的表单数据(前端篇)

1.写在之前 本文前端采用Vue element-plus技术栈&#xff0c;前端项目参考yudao-ui-admin-vue3项目与Geeker-Admin项目。 这篇文章是el-form与el-upload结合上传带附件的表单数据&#xff08;后端篇&#xff09;-CSDN博客姐妹篇&#xff0c;后端篇文章主要讲的是后端的实现逻…

ROS学习笔记(七)---参数服务器

ROS学习笔记文章目录 01. ROS学习笔记(一)—Linux安装VScode 02. ROS学习笔记(二)—使用 VScode 开发 ROS 的Python程序&#xff08;简例&#xff09; 03. ROS学习笔记(三)—好用的终端Terminator 04. ROS学习笔记(四)—使用 VScode 启动launch文件运行多个节点 05. ROS学习笔…

vscode颜色主题插件one dark Pro安装

1.点击扩展图标→搜索“one dark Pro”→第一个点击安装 2.安装成功后&#xff0c;不要忘了点击设置颜色主题 3.看下效果&#xff1a;

MySQL数据库:表的约束

目录 一. 空属性null 二. 默认值default 三. 列描述comment 四. 填充零zerofill 五. 主键primary key 六. 自增长auto_increment 七. 唯一键unique 八. 外键foreign key 一. 空属性null 对于表中的数据&#xff0c;如果在进行插入的时候不显示地给定值&#xff0c;那么…

vp与vs联合开发-通过FrameGrabber连接相机

添加控件 1.CogRecordDisplay 控件 用于显示图像 初始化相机对象方法 //启动窗体时 调用初始化相机方法 //封装相机关闭方法 //窗体关闭时 调用相机关闭方法 拍照 设置采图事件 // 保存图像 设置曝光按钮事件 1.可变参数

css实现0.5px宽度/高度显——属性: transform: scale

在大多数设备上&#xff0c;实际上无法直接使用 CSS 来精确地创建 0.5 像素的边框。因为大多数屏幕的最小渲染单位是一个物理像素&#xff0c;所以通常只能以整数像素单位渲染边框。但是&#xff0c;有一些技巧可以模拟出看起来像是 0.5 像素的边框。 这里介绍使用&#xff1a…

云原生系列2-GitLab和Jenkins

1、GitLab类似github&#xff0c;是个私有仓库 1、GitLab安装&#xff0c;至少8G内存4核cpu # 查找Gitlab镜像 docker search gitlab/gitlab-ce # gitlab镜像拉取 docker pull gitlab/gitlab-ce # 查看镜像 docker images # 本机先建3个目录&#xff0c;为了gitlab容器通过挂…

新增工具箱管理功能、重构网站证书管理功能,1Panel开源面板v1.9.0发布

2023年12月18日&#xff0c;现代化、开源的Linux服务器运维管理面板1Panel正式发布v1.9.0版本。 在这一版本中&#xff0c;1Panel引入了新的工具箱管理功能&#xff0c;包含Swap分区管理、Fail2Ban管理等功能。此外&#xff0c;1Panel针对网站证书管理功能进行了全面重构&…

【数据结构】迷宫问题报告+源码C/C++

之前帮别人写的一个报告&#xff0c;是关于栈的迷宫问题。内容不多&#xff0c;代码在最后。分享给大家&#xff0c;喜欢可以点赞关注。原创无偿分享&#xff0c;勿商用。 迷宫求解 设计目的 仅认识到栈是一种特殊的线性表是远远不够的&#xff0c;本次实习的目的在于使学生…

【Linux基础】3. 文件基本属性

文章目录 【 1. 文件的属主和属组 】【 2. 显示文件的类型、权限 】2.1 文件类型2.2 文件权限 【 3. 更改文件属性 】3.1 chgrp 更改文件属组3.2 chown 更改文件所有者3.3 更改文件权限3.3.1 数字法更改文件权限3.3.2 符号法更改文件权限 【 1. 文件的属主和属组 】 Linux 系统…

Vue的脚手架

脚手架配置 脚手架文档&#xff1a;Vue CLI npm config set registry https://registry.npm.taobao.org vue.config.js配置选项&#xff1a; 配置参考 | Vue CLI ref选项 ref和id类似&#xff0c;给标签打标识。 document.getElementById(btn); this.$ref.btn; 父子组…

Kafka 分级存储在腾讯云的实践与演进

导语 腾讯云消息队列 Kafka 内核负责人鲁仕林为大家带来了《Kafka 分级存储在腾讯云的实践与演进》的精彩分享&#xff0c;从 Kafka 架构遇到的问题与挑战、Kafka 弹性架构方案类比、Kafka 分级存储架构及原理以及腾讯云的落地与实践四个方面详细分享了 Kafka 分级存储在腾讯云…

基于JAVA的海南旅游景点推荐系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

STM32 RTC总结

RTC入侵检测Tamper RTC Tamper功能就是&#xff0c;MCU在Tamper管脚检测到一个指定边缘信号&#xff08;可配置&#xff09;时&#xff0c;就主动清除所有备份寄存器数据的功能。如果需要&#xff0c;可以使能Tamper中断&#xff0c;在每次检测到Tamper信号后执行指定代码。 在…

用23种设计模式打造一个cocos creator的游戏框架----(二十一)组合模式

1、模式标准 模式名称&#xff1a;组合模式 模式分类&#xff1a;结构型 模式意图&#xff1a;将对象组合成树型结构以表示“部分-整体”的层次结构。Composite 使得用户对单个对象和组合对象的使用具有一致性。 结构图&#xff1a; 适用于&#xff1a; 1、想表示对象的部分…

算法通关村第十关—快速排序(青铜)

快速排序 快排的基本过程 快速排序是将分治法运用到排序问题的典型例子  快速排序基本思想是&#xff1a;通过一个标记pivot元素将n个元素的序列划分为左右两个子序列left和right,.其中left中的元素都比pivot小&#xff0c;right的都比pivot的大&#xff0c;然后再次对Ieft和r…

关于折线回归

一、说明 今天的帖子主要是关于使用折线回归找到最佳值。即将某条曲线分解成包络线段&#xff0c;然后用分段回归方式优化。但它也涉及使用 SAS 和 R 的剂量反应研究和样条曲线。这不是第一篇关于这些主题的文章&#xff0c;但我确实想在其中添加折线。只是因为它还在使用。 二…

使用docker-compose搭建docker私服与配置WebUI

简介 本文介绍了使用docker compose 搭建 docker私服 环境 Docker version 24.0.6, build ed223bc Docker Compose version v2.21.0 正文 一、创建registry文件夹 我的路径是/usr/loca/docker/registry 二、创建并编写docker-compose.yml version: "3.9" services…

ios备忘录怎么导入华为 方法介绍

作为一个常常需要在不同设备间切换的人&#xff0c;我深知备忘录的重要性。那些突如其来的灵感、重要的会议提醒、甚至是生活中的琐碎小事&#xff0c;我们都习惯性地记录在备忘录里。但当我决定从iPhone转向华为时&#xff0c;一个问题困扰了我&#xff1a;如何将那些珍贵的备…