一看就会的Next.js App Router版 -- Data Fetching(二)

简介: 一看就会的Next.js App Router版 -- Data Fetching

app/artist/[username]/page.tsx

import 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 };
}) {
  // Initiate both requests in parallel
  const artistData = getArtist(username);
  const albumsData = getArtistAlbums(username);
  // Wait for the promises to resolve
  const [artist, albums] = await Promise.all([artistData, albumsData]);
  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  );
}

By starting the fetch prior to calling await in the Server Component, each request can eagerly start to fetch requests at the same time. This sets the components up so you can avoid waterfalls.

通过在服务器组件中调用 await 之前开始获取,每个请求都可以同时急切地开始获取请求。这会设置组件,这样您就可以避免瀑布。

We can save time by initiating both requests in parallel, however, the user won't see the rendered result until both promises are resolved.

我们可以通过并行发起两个请求来节省时间,但是,在两个承诺都得到解决之前,用户不会看到呈现的结果。

To improve the user experience, you can add a suspense boundary to break up the rendering work and show part of the result as soon as possible:

为了提高用户体验,可以添加一个悬念边界来打散渲染工作,尽快展示部分结果:

artist/[username]/page.tsx

import { getArtist, getArtistAlbums, type Album } from './api';
export default async function Page({
  params: { username },
}: {
  params: { username: string };
}) {
  // Initiate both requests in parallel
  const artistData = getArtist(username);
  const albumData = getArtistAlbums(username);
  // Wait for the artist's promise to resolve first
  const artist = await artistData;
  return (
    <>
      <h1>{artist.name}</h1>
      {/* Send the artist information first,
          and wrap albums in a suspense boundary */}
      <Suspense fallback={<div>Loading...</div>}>
        <Albums promise={albumData} />
      </Suspense>
    </>
  );
}
// Albums Component
async function Albums({ promise }: { promise: Promise<Album[]> }) {
  // Wait for the albums promise to resolve
  const albums = await promise;
  return (
    <ul>
      {albums.map((album) => (
        <li key={album.id}>{album.name}</li>
      ))}
    </ul>
  );
}

Take a look at the preloading pattern for more information on improving components structure.

查看预加载模式以获取有关改进组件结构的更多信息。

Sequential Data Fetching

顺序数据获取

To fetch data sequentially, you can fetch directly inside the component that needs it, or you can await the result of fetch inside the component that needs it:

要顺序获取数据,可以直接在需要它的组件内部获取,也可以在需要它的组件内部等待获取结果:

app/artist/page.tsx

// ...
async function Playlists({ artistID }: { artistID: string }) {
  // Wait for the playlists
  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 };
}) {
  // Wait for the artist
  const artist = await getArtist(username);
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  );
}

By fetching data inside the component, each fetch request and nested segment in the route cannot start fetching data and rendering until the previous request or segment has completed.

通过在组件内部获取数据,路由中的每个获取请求和嵌套段在前一个请求或段完成之前无法开始获取数据和呈现。

Blocking Rendering in a Route

在路由中阻塞渲染

By fetching data in a layout, rendering for all route segments beneath it can only start once the data has finished loading.

通过在布局中获取数据,它下面的所有路线段的渲染只能在数据加载完成后才能开始。

In the pages directory, pages using server-rendering would show the browser loading spinner until getServerSideProps had finished, then render the React component for that page. This can be described as "all or nothing" data fetching. Either you had the entire data for your page, or none.

在 pages 目录中,使用服务器渲染的页面将显示浏览器加载微调器,直getServerSideProps 完成,然后为该页面渲染 React 组件。这可以描述为“全有或全无”的数据获取。您拥有页面的全部数据,或者没有。

In the app directory, you have additional options to explore:

在 app 目录中,您可以探索其他选项:

  1. First, you can use loading.js to show an instant loading state from the server while streaming in the result from your data fetching function.
    首先,您可以使用 loading.js 显示来自服务器的即时加载状态,同时从您的数据获取函数中流式传输结果。
  2. Second, you can move data fetching lower in the component tree to only block rendering for the parts of the page that need it. For example, moving data fetching to a specific component rather than fetching it at the root layout.
    其次,您可以将数据获取移至组件树中较低的位置,以仅阻止对需要它的页面部分进行渲染。例如,将数据获取移动到特定组件,而不是在根布局中获取。

Whenever possible, it's best to fetch data in the segment that uses it. This also allows you to show a loading state for only the part of the page that is loading, and not the entire page.

只要有可能,最好在使用它的段中获取数据。这还允许您仅显示正在加载的页面部分的加载状态,而不是整个页面。

Data Fetching without fetch()

没有 fetch() 的数据获取

You might not always have the ability to use and configure fetch requests directly if you're using a third-party library such as an ORM or database client.

如果您使用第三方库(例如 ORM 或数据库客户端),您可能并不总是能够直接使用和配置获取请求。

In cases where you cannot use fetch but still want to control the caching or revalidating behavior of a layout or page, you can rely on the default caching behavior of the segment or use the segment cache configuration.

如果您不能使用 fetch 但仍想控制布局或页面的缓存或重新验证行为,您可以依赖段的默认缓存行为或使用段缓存配置。

Default Caching Behavior

默认缓存行为

Any data fetching libraries that do not use fetch directly will not affect caching of a route, and will be static or dynamic depending on the route segment.

任何不直接使用 fetch 的数据抓取库都不会影响路由的缓存,根据路由段的不同会是静态的还是动态的。

If the segment is static (default), the output of the request will be cached and revalidated (if configured) alongside the rest of the segment. If the segment is dynamic, the output of the request will not be cached and will be re-fetched on every request when the segment is rendered.

如果段是静态的(默认),请求的输出将与段的其余部分一起缓存和重新验证(如果已配置)。如果段是动态的,则请求的输出将不会被缓存,并且会在呈现段时在每个请求上重新获取。

Good to know: Dynamic functions like cookies() and headers() will make the route segment dynamic.

提示:cookies() 和 headers() 等动态函数将使路由段动态化。

Segment Cache Configuration

段缓存配置

As a temporary solution, until the caching behavior of third-party queries can be configured, you can use segment configuration to customize the cache behavior of the entire segment.

作为临时解决方案,在可以配置第三方查询的缓存行为之前,可以使用段配置来自定义整个段的缓存行为。

app/page.tsx

import prisma from './lib/prisma';
export const revalidate = 3600; // revalidate every hour
async function getPosts() {
  const posts = await prisma.post.findMany();
  return posts;
}
export default async function Page() {
  const posts = await getPosts();
  // ...
}

Caching Data

缓存数据

Next.js has built-in support for caching data, both on a per-request basis (recommended) or for an entire route segment.

Next.js 内置了对缓存数据的支持,无论是基于每个请求(推荐)还是针对整个路由段。

image.png

Per-request Caching

按请求缓存

fetch()

By default, all fetch() requests are cached and deduplicated automatically. This means that if you make the same request twice, the second request will reuse the result from the first request.

默认情况下,所有的 fetch() 请求都会被自动缓存和去重。这意味着如果您两次发出相同的请求,第二个请求将重用第一个请求的结果。

app/page.tsx

async function getComments() {
  const res = await fetch('https://...'); // The result is cached
  return res.json();
}
// This function is called twice, but the result is only fetched once
const comments = await getComments(); // cache MISS
// The second call could be anywhere in your application
const comments = await getComments(); // cache HIT

Requests are not cached if:

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

  • Dynamic methods (next/headers, export const POST, or similar) are used and the fetch is a POST request (or uses Authorization or cookie headers)
  • 使用动态方法(next/headers、export const POST 或类似方法)并且获取是 POST 请求(或使用授权或 cookie 标头)
  • fetchCache is configured to skip cache by default
  • fetchCache 默认配置为跳过缓存
  • revalidate: 0 or cache: 'no-store' is configured on individual fetch
  • revalidate: 0 or cache: 'no-store' 是在单独获取时配置的

Requests made using fetch can specify a revalidate option to control the revalidation frequency of the request.

使用 fetch 发出的请求可以指定一个重新验证选项来控制请求的重新验证频率。

app/page.tsx

export default async function Page() {
  // revalidate this data every 10 seconds at most
  const res = await fetch('https://...', { next: { revalidate: 10 } });
  const data = res.json();
  // ...
}

React cache()

React allows you to cache() and deduplicate requests, memoizing the result of the wrapped function call. The same function called with the same arguments will reuse a cached value instead of re-running the function.

React 允许你缓存()  并删除重复请求,记住包装函数调用的结果。使用相同参数调用的相同函数将重用缓存值,而不是重新运行该函数。

utils/getUser.ts

import { cache } from 'react';
export const getUser = cache(async (id: string) => {
  const user = await db.user.findUnique({ id });
  return user;
});


import { getUser } from '@utils/getUser';
export default async function UserLayout({ params: { id } }) {
  const user = await getUser(id);
  // ...
}
目录
相关文章
|
30天前
|
小程序 JavaScript 前端开发
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
uni-app开发微信小程序:四大解决方案,轻松应对主包与vendor.js过大打包难题
504 1
|
2月前
|
监控 JavaScript 前端开发
深入理解 Nuxt.js 中的 app:error 钩子
【9月更文挑战第25天】在 Nuxt.js 中,`app:error` 钩子是一个强大的工具,用于处理应用程序中的各种错误。它可以在服务器端渲染或客户端运行时触发,帮助提升应用稳定性和用户体验。通过在 `nuxt.config.js` 中定义该钩子,开发者可以实现错误页面显示、错误日志记录及错误恢复等功能,确保应用在遇到问题时能妥善处理并恢复正常运行。
48 10
|
3月前
|
资源调度 JavaScript Linux
【Azure 应用服务】本地Node.js部署上云(Azure App Service for Linux)遇到的三个问题解决之道
【Azure 应用服务】本地Node.js部署上云(Azure App Service for Linux)遇到的三个问题解决之道
|
2月前
|
开发者 UED
深入理解 Nuxt.js 中的 app:error 钩子
【9月更文挑战第26天】在 Nuxt.js 中,钩子函数是在特定生命周期阶段执行代码的机制,`app:error` 钩子用于处理应用中的错误,包括服务器端和客户端渲染时出现的问题。它提供了一个集中处理错误的机制,提升了用户体验。当组件渲染过程中出现错误时,`app:error` 钩子会被触发,可以在 `nuxt.config.js` 文件中定义该钩子。通过分析错误对象 `err` 和上下文信息 `context`,开发者可以更好地处理各种错误情况。相比组件内的 `try/catch` 或浏览器原生错误处理,`app:error` 提供了更全局和有针对性的错误处理方式。
|
3月前
【Azure Function App】在ADF(Azure Data Factory)中调用 Azure Function 时候遇见 Failed to get MI access token
【Azure Function App】在ADF(Azure Data Factory)中调用 Azure Function 时候遇见 Failed to get MI access token
|
3月前
|
JavaScript 前端开发
【Azure Developer】在App Service上放置一个JS页面并引用msal.min.js成功获取AAD用户名示例
【Azure Developer】在App Service上放置一个JS页面并引用msal.min.js成功获取AAD用户名示例
|
3月前
|
JavaScript 前端开发 UED
揭秘Vue.js高效开发:Vue Router如何让单页面应用路由管理变得如此简单?
【8月更文挑战第30天】随着Web应用复杂性的增加,单页面应用(SPA)因出色的用户体验和高效的页面加载性能而备受青睐。Vue.js凭借简洁的语法和灵活的组件系统成为构建SPA的热门选择,其官方路由管理器Vue Router则简化了路由管理。本文通过实战示例介绍如何利用Vue Router实现高效的SPA路由管理,包括命名路由、动态路由及其核心优势。
35 0
|
3月前
|
网络协议
【Azure 应用服务】Azure Data Factory中调用Function App遇见403 - Forbidden
【Azure 应用服务】Azure Data Factory中调用Function App遇见403 - Forbidden
|
3月前
|
JavaScript Windows
【Azure 应用服务】用App Service部署运行 Vue.js 编写的项目,应该怎么部署运行呢?
【Azure 应用服务】用App Service部署运行 Vue.js 编写的项目,应该怎么部署运行呢?
|
3月前
|
开发框架 JavaScript 前端开发
【Azure Developer】App Service + PubSub +JS 实现多人版黑客帝国文字流效果图
【Azure Developer】App Service + PubSub +JS 实现多人版黑客帝国文字流效果图

热门文章

最新文章