一看就会的Next.js Pages Router版 -- Getting Started(二)

简介: 一看就会的Next.js Pages Router版 -- Getting Started

Where is the Network Boundary?

在 App Router 中,网络边界位于 Server Components 和 Client Components 之间。这与边界位于 getStaticProps/getServerSideProps 和页面组件之间的页面不同。服务器组件内部获取的数据不需要序列化,因为它不会跨越网络边界,除非它被传递给客户端组件。了解有关使用服务器组件获取数据的更多信息。

Keeping Server-Only Code out of Client Components (Poisoning)

由于 JavaScript 模块可以在服务器和客户端组件之间共享,因此原本只打算在服务器上运行的代码有可能偷偷进入客户端。

例如,采用以下数据获取功能:

lib/data.ts
export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  });
  return res.json();
}

乍一看,getData 似乎同时适用于服务器和客户端。但是因为环境变量 API_KEY 没有 NEXT_PUBLIC 前缀,所以它是一个私有变量,只能在服务器上访问。 Next.js 在客户端代码中将私有环境变量替换为空字符串,以防止泄露安全信息。

因此,即使可以在客户端导入并执行 getData(),它也不会按预期工作。虽然公开变量会使函数在客户端上运行,但它会泄露敏感信息。

因此,编写此函数的意图是它只会在服务器上执行。

The "server only" package

为了防止这种客户端无意识地使用服务器代码,我们可以使用 server-only 包来给其他开发人员一个构建时错误,如果他们不小心将这些模块之一导入客户端组件。

要仅使用服务器,请先安装软件包:

npm install server-only

然后将包导入任何包含仅服务器代码的模块:

lib/data.js
import 'server-only';
export async function getData() {
  const res = await fetch('https://external-service.com/data', {
    headers: {
      authorization: process.env.API_KEY,
    },
  });
  return res.json();
}

现在,任何导入 getData() 的客户端组件都会收到一个构建时错误,说明该模块只能在服务器上使用。

The corresponding package client-only can be used to mark modules that contain client-only code – for example, code that accesses the window object.

相应的包 client-only 可用于标记包含仅客户端代码的模块——例如,访问窗口对象的代码。

Data Fetching

虽然可以在客户端组件中获取数据,但我们建议在服务器组件中获取数据,除非您有特定原因需要在客户端获取数据。将数据提取移动到服务器可以带来更好的性能和用户体验。

Learn more about data fetching.

Third-party packages

由于服务器组件是新的,生态系统中的第三方包才刚刚开始向使用仅客户端功能(如 useState、useEffect 和 createContext)的组件添加“使用客户端”指令。

今天,许多使用仅客户端功能的 npm 包中的组件还没有该指令。这些第三方组件将在您自己的客户端组件中按预期工作,因为它们具有“使用客户端”指令,但它们不会在服务器组件中工作。

例如,假设您已经安装了假设的 acme-carousel 包,它有一个  组件。该组件使用 useState,但还没有“使用客户端”指令。

如果您在客户端组件中使用 <AcmeCarousel />,它将按预期工作:

app/gallery.tsx
'use client';
import { useState } from 'react';
import { AcmeCarousel } from 'acme-carousel';
export default function Gallery() {
  let [isOpen, setIsOpen] = useState(false);
  return (
    <div>
      <button onClick={() => setIsOpen(true)}>View pictures</button>
      {/* 🟢 Works, since AcmeCarousel is used within a Client Component */}
      {isOpen && <AcmeCarousel />}
    </div>
  );
}

但是,如果您尝试在服务器组件中直接使用它,您会看到一个错误:

js

复制代码

app/page.tsx
import { AcmeCarousel } from 'acme-carousel';
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
      {/* 🔴 Error: `useState` can not be used within Server Components */}
      <AcmeCarousel />
    </div>
  );
}

这是因为 Next.js 不知道 <AcmeCarousel /> 正在使用仅限客户端的功能。

要解决此问题,您可以将依赖于仅客户端功能的第三方组件包装在您自己的客户端组件中:

app/carousel.tsx
'use client';
import { AcmeCarousel } from 'acme-carousel';
export default AcmeCarousel;

现在,您可以直接在服务器组件中使用 <Carousel />

app/page.tsx
import Carousel from './carousel';
export default function Page() {
  return (
    <div>
      <p>View pictures</p>
      {/* 🟢 Works, since Carousel is a Client Component */}
      <Carousel />
    </div>
  );
}

We don't expect you to need to wrap most third-party components since it's likely you'll be using them within Client Components. However, one exception is provider components, since they rely on React state and context, and are typically needed at the root of an application. Learn more about third-party context providers below.

我们不希望您需要包装大多数第三方组件,因为您很可能会在客户端组件中使用它们。然而,一个例外是提供者组件,因为它们依赖于 React 状态和上下文,并且通常在应用程序的根部需要。在下面了解有关第三方上下文提供程序的更多信息。

Library Authors

以类似的方式,创建供其他开发人员使用的包的库作者可以使用“use client”指令来标记其包的客户端入口点。这允许包的用户将包组件直接导入到他们的服务器组件中,而不必创建包装边界。

  • You can optimize your package by using 'use client' deeper in the tree, allowing the imported modules to be part of the Server Component module graph.
  • 您可以通过在树的更深处使用“使用客户端”来优化您的包,允许导入的模块成为服务器组件模块图的一部分。
  • It's worth noting some bundlers might strip out "use client" directives. You can find an example of how to configure esbuild to include the "use client" directive in the React Wrap Balancer and Vercel Analytics repositories.

值得注意的是,一些打包器可能会去掉“使用客户端”指令。你可以在React Wrap Balancer和Vercel Analytics存储库中找到一个如何配置esbuild以包含“use client”指令的示例。

Context

Most React applications rely on context to share data between components, either directly via createContext, or indirectly via provider components imported from third-party libraries.

大多数React应用依赖上下文在组件之间共享数据,要么直接通过createContext,要么间接通过从第三方库导入的提供商组件。

In Next.js 13, context is fully supported within Client Components, but it cannot be created or consumed directly within Server Components. This is because Server Components have no React state (since they're not interactive), and context is primarily used for rerendering interactive components deep in the tree after some React state has been updated.

在 Next.js 13 中,客户端组件完全支持上下文,但不能直接在服务器组件中创建或使用它。这是因为服务器组件没有 React 状态(因为它们不是交互式的),并且上下文主要用于在某些 React 状态更新后重新呈现树深处的交互式组件。

We'll discuss alternatives for sharing data between Server Components, but first, let's take a look at how to use context within Client Components.

我们将讨论在服务器组件之间共享数据的备选方案,但首先让我们看一下如何在客户端组件中使用上下文。

All of the context APIs are fully supported within Client Components:

客户端组件完全支持所有上下文 API:

app/sidebar.tsx
'use client';
import { createContext, useContext, useState } from 'react';
const SidebarContext = createContext();
export function Sidebar() {
  const [isOpen, setIsOpen] = useState();
  return (
    <SidebarContext.Provider value={{ isOpen }}>
      <SidebarNav />
    </SidebarContext.Provider>
  );
}
function SidebarNav() {
  let { isOpen } = useContext(SidebarContext);
  return (
    <div>
      <p>Home</p>
      {isOpen && <Subnav />}
    </div>
  );
}

However, context providers are typically rendered near the root of an application to share global concerns, like the current theme. Because context is not supported in Server Components, trying to create a context at the root of your application will cause an error:

但是,上下文提供程序通常呈现在应用程序的根附近以共享全局关注点,例如当前主题。由于服务器组件不支持上下文,因此尝试在应用程序的根目录下创建上下文会导致错误:

app/layout.tsx
import { createContext } from 'react';
// 🔴 createContext is not supported in Server Components
export const ThemeContext = createContext({});
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <ThemeContext.Provider value="dark">{children}</ThemeContext.Provider>
      </body>
    </html>
  );
}

To fix this, create your context and render its provider inside of a Client Component:

要解决此问题,请创建您的上下文并在客户端组件中呈现其提供者:

app/theme-provider.tsx
'use client';
import { createContext } from 'react';
export const ThemeContext = createContext({});
export default function ThemeProvider({ children }) {
  return 
  <ThemeContext.Provider value="dark">
      {children}
  </ThemeContext.Provider>;
}

Your Server Component will now be able to directly render your provider since it's been marked as a Client Component:

您的服务器组件现在可以直接呈现您的提供者,因为它已被标记为客户端组件:

app/layout.tsx
import ThemeProvider from './theme-provider';
export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

With the provider rendered at the root, all other Client Components throughout your app will be able to consume this context.

通过在根节点呈现该提供商,应用中的所有其他客户端组件都可以使用该上下文。

Note: You should render providers as deep as possible in the tree – notice how ThemeProvider only wraps {children} instead of the entire <html> document. This makes it easier for Next.js to optimize the static parts of your Server Components.

注意:您应该在树中尽可能深地呈现提供者——注意 ThemeProvider 如何仅包装 {children} 而不是整个  文档。这使 Next.js 更容易优化服务器组件的静态部分。

Rendering third-party context providers in Server Components

Third-party npm packages often include Providers that need to be rendered near the root of your application. If these providers include the "use client" directive, they can be rendered directly inside of your Server Components. However, since Server Components are so new, many third-party providers won't have added the directive yet.

第三方 npm 包通常包含需要在应用程序根目录附近呈现的提供程序。如果这些提供者包含“使用客户端”指令,则它们可以直接在您的服务器组件内呈现。但是,由于服务器组件非常新,许多第三方提供商还没有添加该指令。

If you try to render a third-party provider that doesn't have "use client", it will cause an error:

如果您尝试呈现没有“使用客户端”的第三方提供程序,则会导致错误:

 app/layout.tsx
import { ThemeProvider } from 'acme-theme';
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {/* 🔴 Error: `createContext` can't be used in Server Components */}
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}

To fix this, wrap third-party providers in your own Client Component:

要解决此问题,请将第三方提供商包装在您自己的客户端组件中:

    app/providers.js
'use client';
import { ThemeProvider } from 'acme-theme';
import { AuthProvider } from 'acme-auth';
export function Providers({ children }) {
  return (
    <ThemeProvider>
      <AuthProvider>{children}</AuthProvider>
    </ThemeProvider>
  );
}

Now, you can import and render <Providers /> directly within your root:

现在,您可以直接在根目录中导入和呈现 <Providers />

import { Providers } from './providers';
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

With the providers rendered at the root, all the components and hooks from these libraries will work as expected within your own Client Components.

通过在根目录中呈现提供程序,这些库中的所有组件和挂钩都将在您自己的客户端组件中按预期工作。

Once a third-party library has added "use client" to its client code, you'll be able to remove the wrapper Client Component.

一旦第三方库将“使用客户端”添加到其客户端代码中,您就可以删除包装客户端组件。

Sharing data between Server Components

在服务器组件之间共享数据

Since Server Components are not interactive and therefore do not read from React state, you don't need the full power of context to share data. You can use native JavaScript patterns like global singletons within module scope if you have common data that multiple Server Component need to access.

由于服务器组件不是交互式的,因此不会从 React 状态读取,因此您不需要上下文的全部功能来共享数据。如果您有多个服务器组件需要访问的公共数据,您可以在模块范围内使用本地 JavaScript 模式,如全局单例。

For example, a module can be used to share a database connection across multiple components:

例如,一个模块可用于跨多个组件共享数据库连接:

    utils/database.ts
export const db = new DatabaseConnection(...);
    app/users/layout.tsx
import { db } from "@utils/database";
export async function UsersLayout() {
  let users = await db.query(...);
  // ...
}
    app/users/[id]/page.tsx
import { db } from "@utils/database";
export async function DashboardPage() {
  let user = await db.query(...);
  // ...
}

In the above example, both the layout and page need to make database queries. Each of these components shares access to the database by importing the @utils/database module.

在上面的例子中,布局和页面都需要进行数据库查询。这些组件中的每一个都通过导入 @utils/database 模块来共享对数据库的访问。

Sharing fetch requests between Server Components

在服务器组件之间共享获取请求

When fetching data, you may want to share the result of a fetch between a page or layout and some of its children components. This is an unnecessary coupling between the components and can lead to passing props back and forth between components.

获取数据时,您可能希望在页面或布局与其某些子组件之间共享获取结果。这是组件之间不必要的耦合,可能导致组件之间来回传递 props。

Instead, we recommend colocating data fetching alongside the component that consumes the data. fetch requests are automatically deduped in Server Components, so each route segment can request exactly the data it needs without worrying about duplicate requests. Next.js will read the same value from the fetch cache.

相反,我们建议将数据获取与使用数据的组件放在一起。 fetch 请求在 Server Components 中自动进行重复数据删除,因此每个路由段都可以准确地请求它需要的数据,而不必担心重复请求。 Next.js 将从获取缓存中读取相同的值。

目录
相关文章
|
9月前
|
JavaScript 开发者 容器
Vue Router:构建交互性Vue.js应用的导航之道
在Vue.js应用程序开发中,导航是一个关键概念,它允许用户在不同视图之间进行无缝切换和交互。而Vue Router是Vue.js官方提供的路由管理库,为开发者提供了强大的工具来管理应用程序的导航和路由。在本博客中,我们将深入研究Vue Router的概念、核心功能、工作原理,以及如何使用Vue Router来构建具有高度交互性的Vue.js应用。
67 0
|
8月前
|
JavaScript 前端开发 Go
Vue Router入门:为Vue.js应用添加导航
Vue Router入门:为Vue.js应用添加导航
63 0
|
9月前
|
JavaScript 数据安全/隐私保护 网络架构
Vue Router最佳实践,以确保你的Vue.js应用的路由管理清晰、可维护和高效
以下是使用Vue Router的最佳实践,以确保你的Vue.js应用的路由管理清晰、可维护和高效。
129 0
|
存储 JavaScript 前端开发
一看就会的Next.js App Router版 -- Getting Started
一看就会的Next.js App Router版 -- Getting Started
289 0
|
Web App开发 缓存 前端开发
一看就会的Next.js Pages Router版 -- Getting Started(一)
一看就会的Next.js Pages Router版 -- Getting Started
177 0
|
资源调度 JavaScript 前端开发
【0成本搭建个人博客】——Hexo+Node.js+Gitee Pages
【0成本搭建个人博客】——Hexo+Node.js+Gitee Pages
117 0
|
移动开发 JSON 前端开发
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境2
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境
236 0
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境2
|
移动开发 前端开发 JavaScript
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境
217 0
Vite 2.x + React + Zarm + Less + React Router v6 + Axios + flexible.js 搭建前端 H5 开发环境
|
JavaScript 开发工具 git
用Node.js和GitHub Pages搭建博客 0504
Node.js +GitHub Pages搭建静态博客;我好像总是犯傻;以及其他
1984 0