中文

探索 Next.js App Directory 中变革性的基于文件的路由系统,为现代 Web 应用程序提供增强的组织性、性能和开发者体验。

Next.js App Directory:基于文件的路由变革

Next.js 一直在推动 Web 开发的边界,为开发人员提供强大的工具和功能来构建高性能、可扩展且用户友好的应用程序。App Directory 的引入代表着一个重要的飞跃,尤其是在其创新的基于文件的路由方法中。本文深入探讨 App Directory 的路由机制,探讨其优势、关键概念以及使用 Next.js 构建现代 Web 应用程序的实际意义。

了解 Next.js 中路由的演变

在 App Directory 之前,Next.js 依赖于 Pages Directory 进行路由。虽然有效,但这种方法有一些局限性。Pages Directory 使用一个简单的基于文件的路由系统,其中 `pages` 目录中的每个文件对应于一个路由。例如,`pages/about.js` 将映射到 `/about` 路由。

虽然 Pages Directory 很简单,但它缺乏对复杂布局、数据获取策略和服务器端渲染模式的内置支持,通常需要开发人员手动实现这些功能。此外,数据获取和组件渲染的紧密耦合有时会导致性能瓶颈。

App Directory 通过引入一个基于 React Server Components、Layouts 和其他高级功能构建的更灵活、更强大的路由系统来解决这些限制。它超越了简单的文件到路由的映射,并提供了一种更具声明性和可组合性的方法来定义应用程序路由和布局。

介绍 App Directory:路由的新范例

App Directory 位于 Next.js 项目的根目录中的 `app` 文件夹中,它引入了一种从根本上不同的路由方法。App Directory 没有直接将文件映射到路由,而是使用一种基于约定的系统,其中目录和特殊文件的结构决定了应用程序的路由

这种方法提供了几个关键优势:

App Directory 的路由系统中的关键概念

为了有效地利用 App Directory 的路由系统,必须了解构成其功能的关键概念:

1. 路由段和文件夹

`app` 目录中的每个文件夹代表一个 路由段。文件夹的名称对应于 URL 中的路径段。例如,`app/blog/posts` 文件夹结构将映射到 `/blog/posts` 路由。

考虑以下结构:

app/
  blog/
    posts/
      page.js

此结构在 `/blog/posts` 定义了一个路由。`posts` 文件夹中的 `page.js` 文件是 路由段组件,它呈现该路由的内容。

2. `page.js` 文件:呈现路由内容

page.js(或 TypeScript 的 page.tsx)文件是一个特殊文件,用于定义要为特定路由段呈现的内容。它是该路由的入口点。此文件必须将 React 组件导出为其默认导出。

示例:

// app/blog/posts/page.js

export default function PostsPage() {
  return (
    <div>
      <h1>博客文章</h1>
      <p>博客文章列表将在此处显示。</p>
    </div>
  );
}

3. 布局:定义共享 UI

布局 允许您定义跨多个页面或路由段共享的 UI。布局可以包含元素,例如标题、页脚、侧边栏或任何应在应用程序的某个部分中保持一致的其他组件。布局使用 `layout.js`(或 `layout.tsx`)文件定义。

布局是嵌套的。这意味着根布局 (`app/layout.js`) 包装整个应用程序,嵌套布局包装特定的路由段。在共享布局的路由之间导航时,Next.js 会保留布局的状态并避免重新呈现它,从而提高性能并提供更流畅的用户体验。

示例:

// app/layout.js

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <header>
          <nav>
            <a href="/">主页</a> |
            <a href="/blog">博客</a>
          </nav>
        </header>
        <main>{children}</main>
        <footer>
          <p>版权所有 2023</p>
        </footer>
      </body>
    </html>
  );
}

在此示例中,`RootLayout` 定义了整个应用程序的基本 HTML 结构、标题、页脚和导航。`app` 目录中呈现的任何页面都将被此布局包装。

4. 模板:在路由之间保留状态

与布局类似,模板 也包装子路由。但是,与布局不同,模板为每个子路由创建一个新的组件实例。这意味着在模板内的路由之间导航时,模板的状态不会保留。模板对于需要在路由转换时重置或重新初始化状态的场景很有用。使用 template.js(或 template.tsx)创建模板。

5. 路由组:组织路由而不使用 URL 段

路由组 允许您在 App Directory 中组织路由,而不会影响 URL 结构。路由组通过将文件夹名称用括号括起来来定义,例如 `(group-name)`。这些括号告诉 Next.js 将该文件夹视为逻辑分组机制,而不是路由段。

这对于组织具有许多路由的大型应用程序特别有用。例如,您可以使用路由组来分隔应用程序的不同部分,例如 `(marketing)` 和 `(app)`。这些组只会影响文件结构,而不会影响 URL 路径。

示例:

app/
  (marketing)/
    home/
      page.js  // 可在 /home 访问
    about/
      page.js  // 可在 /about 访问
  (app)/
    dashboard/
      page.js  // 可在 /dashboard 访问

6. 动态路由:处理可变段

动态路由允许您创建具有可变段的路由。这对于需要根据数据生成路由的场景很有用,例如博客文章、产品页面或用户个人资料。动态路由段通过将段名称括在方括号中来定义,例如 `[id]`。`id` 表示一个参数,可以在 `page.js` 组件中访问该参数。

示例:

app/
  blog/
    [slug]/
      page.js

在此示例中,`[slug]` 是一个动态路由段。类似于 `/blog/my-first-post` 的 URL 将匹配此路由,并且 `slug` 参数将设置为 `my-first-post`。您可以使用 `params` prop 在 `page.js` 组件中访问 `slug` 参数。

// app/blog/[slug]/page.js

export default function BlogPost({ params }) {
  const { slug } = params;
  return (
    <div>
      <h1>博客文章:{slug}</h1>
      <p>带有 slug 的博客文章的内容:{slug}</p>
    </div>
  );
}

您需要为这些动态路由生成可能的值。Next.js 为静态站点生成 (SSG) 和服务器端渲染 (SSR) 提供了 `generateStaticParams` 函数。此函数允许您指定应在构建时预呈现哪些动态路由。

// app/blog/[slug]/page.js

export async function generateStaticParams() {
  const posts = [
    { slug: 'my-first-post' },
    { slug: 'my-second-post' },
  ];

  return posts.map((post) => ({ slug: post.slug }));
}

export default function BlogPost({ params }) {
  const { slug } = params;
  return (
    <div>
      <h1>博客文章:{slug}</h1>
      <p>带有 slug 的博客文章的内容:{slug}</p>
    </div>
  );
}

7. 捕获所有段:处理未知路由

捕获所有段 是一种动态路由,允许您匹配 URL 中任意数量的段。它们通过在段名称前加上三个点来定义,例如 `[...path]`。捕获所有段对于创建可以处理各种 URL 结构的灵活路由很有用。

示例:

app/
  docs/
    [...path]/
      page.js

在此示例中,`[...path]` 是一个捕获所有段。类似于 `/docs/introduction`、`/docs/api/reference` 和 `/docs/examples/basic` 的 URL 都将匹配此路由。`path` 参数将是一个包含匹配段的数组。

// app/docs/[...path]/page.js

export default function DocsPage({ params }) {
  const { path } = params;
  return (
    <div>
      <h1>文档</h1>
      <p>路径:{path.join('/')}</p>
    </div>
  );
}

8. 并行路由:同时呈现多个页面

并行路由 使您能够同时在同一布局中呈现多个页面。这对于创建复杂的 UI 模式特别有用,例如具有多个面板的仪表板或显示在当前页面顶部的模态对话框。并行路由使用 @ 符号定义,例如 `@children`、`@modal`。它们可以直接在 URL 中指定,也可以使用 `useRouter` hook 导航到。

示例:

app/
  @children/
    page.js // 呈现主要内容
  @modal/
    login/
      page.js // 呈现登录模态

要显示并行路由,请使用 <Slot> 组件。

9. 拦截路由:创建复杂的 UI 转换

拦截路由 允许您在当前路由的上下文中从应用程序的不同部分加载路由。这可用于创建复杂的 UI 转换,例如在单击链接时显示模态对话框,而无需离开当前页面。它们使用 (...) 语法定义。

App Directory 中的数据获取

App Directory 引入了新的和改进的数据获取方法,利用 React Server Components 和带有内置缓存和重新验证功能的 `fetch` API。这带来了更好的性能和更简化的开发体验。服务器和客户端组件都可以获取数据,但策略不同。

1. 服务器组件中的数据获取

服务器组件(App Directory 中的默认组件)可以直接从数据库或 API 获取数据。这在渲染之前的组件函数中完成。由于服务器组件在服务器上执行,因此您可以安全地包含密钥和凭据,而无需将它们暴露给客户端。`fetch` API 会自动进行记忆,这意味着重复的数据请求会被删除重复项,从而进一步提高性能。

// app/page.js

async function getData() {
  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
  // 返回值 *未* 序列化
  // 您可以返回 Date、Map、Set 等。

  if (!res.ok) {
    // 这将激活最接近的 `error.js` 错误边界
    throw new Error('获取数据失败');
  }

  return res.json();
}

export default async function Page() {
  const data = await getData();

  return <div>{data.title}</div>;
}

2. 客户端组件中的数据获取

客户端组件(由文件顶部的 'use client' 指令指示)在用户的浏览器中执行。客户端组件中的数据获取通常涉及使用 `useEffect` hook 和一个库,如 `axios` 或 `fetch` API。服务器操作提供了一种从客户端组件更改服务器数据的安全方式。这为客户端组件提供了一种安全的方式来与服务器上的数据交互,而无需直接暴露 API 端点。

// app/components/ClientComponent.js
'use client';

import { useState, useEffect } from 'react';

export default function ClientComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    async function fetchData() {
      const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
      const data = await res.json();
      setData(data);
    }

    fetchData();
  }, []);

  if (!data) {
    return <div>正在加载...</div>;
  }

  return <div>{data.title}</div>;
}

App Directory 的 SEO 注意事项

App Directory 的服务器优先方法为 SEO 提供了显着的优势。由于内容在服务器上呈现,因此搜索引擎爬虫可以轻松访问和索引页面内容。以下是一些关键的 SEO 注意事项:

使用 App Directory 的路由系统的好处

App Directory 的路由系统提供了许多好处,这些好处可以增强开发过程、提高应用程序性能并为用户带来更好的体验。让我们更详细地探讨这些优势: * **增强的组织性和可维护性:** 基于文件的路由系统本身可以促进结构化和有组织的 codebase。通过将路由直接映射到目录结构,开发人员可以轻松了解 URL 与相应组件之间的关系。这种清晰的结构简化了 codebase 中的导航,并使应用程序的维护和更新随着时间的推移变得更加容易。 * **通过服务器组件提高性能:** App Directory 利用 React Server Components 在服务器上呈现内容,从而减少了需要在浏览器中下载和执行的 JavaScript 数量。这可以缩短初始页面加载时间并提高整体性能,特别是对于互联网连接速度较慢或设备功能较弱的用户。 * **简化的数据获取和管理:** App Directory 通过允许开发人员直接在服务器组件中获取数据来简化数据获取。这消除了对复杂客户端数据获取逻辑的需求,并降低了将敏感数据暴露给客户端的风险。 * **声明式和直观的路由:** 基于文件的路由系统提供了一种声明式和直观的方式来定义应用程序路由。通过简单地在 `app` 目录中创建文件和目录,开发人员可以轻松定义应用程序导航的结构和行为。这种方法减少了对复杂配置文件的需求,并使路由系统更易于理解和使用。 * **用于一致 UI 的内置布局和模板:** App Directory 提供对布局和模板的内置支持,这使开发人员可以定义跨多个页面一致的共享 UI 元素。这减少了代码重复,并使维护整个应用程序的一致外观和感觉变得更加容易。 * **用于复杂用例的高级路由功能:** App Directory 提供了一系列高级路由功能,例如动态路由、捕获所有段、并行路由和拦截路由。这些功能使开发人员能够处理复杂的路由场景并创建复杂的 UI 模式,否则这些模式很难或无法使用传统的路由系统实现。 ## App Directory 路由在实践中的实际示例 为了说明 App Directory 路由系统的强大功能和灵活性,让我们考虑几个实际示例: ### 1. 使用动态路由构建一个简单的博客 考虑一个博客应用程序,其中每篇博客文章都根据其 slug 拥有自己唯一的 URL。使用 App Directory,可以使用动态路由轻松实现此目的: ``` app/ blog/ [slug]/ page.js ``` `[slug]` 目录表示一个动态路由段,它将匹配 `/blog/` 路径下的任何 URL。`[slug]` 目录中的 `page.js` 文件将呈现相应博客文章的内容。 ```javascript // app/blog/[slug]/page.js export async function generateStaticParams() { // 从数据库或 API 获取所有博客文章 const posts = await fetchPosts(); // 将文章映射到 slug 参数数组 return posts.map((post) => ({ slug: post.slug })); } export default async function BlogPost({ params }) { const { slug } = params; // 获取带有匹配 slug 的博客文章 const post = await fetchPost(slug); if (!post) { return <div>找不到文章</div>; } return ( <article> <h1>{post.title}</h1> <p>{post.content}</p> </article> ); } ``` 此示例演示了如何使用动态路由以简单有效的方式为每篇博客文章创建单独的页面。 ### 2. 使用拦截路由实现模态对话框 假设您想实现一个模态对话框,该对话框在用户单击链接时出现,而无需离开当前页面。这可以使用拦截路由来实现: ``` app/ (.)photos/ [id]/ @modal/ page.js page.js ``` 在此,`(.)photos/[id]/@modal/page.js` 拦截来自当前页面的转到 `photos/[id]` 的请求。当用户单击特定照片的链接时,模态对话框将出现在当前页面的顶部,而不是导航到新页面。 ### 3. 使用并行路由创建一个仪表板布局 想象一下,您正在构建一个仪表板应用程序,其中需要同时呈现多个面板。可以使用并行路由来实现此布局: ``` app/ @analytics/ page.js // 分析仪表板 @settings/ page.js // 设置面板 page.js // 主仪表板布局 ```

在此结构中,`@analytics` 和 `@settings` 表示将在主仪表板布局中呈现的并行路由。每个并行路由都有自己的 page.js 文件,用于定义该面板的内容。布局可以使用 <Slot> 组件决定这些路由的放置位置。

从 Pages Directory 迁移到 App Directory

将现有的 Next.js 应用程序从 Pages Directory 迁移到 App Directory 需要仔细的规划和执行。虽然 App Directory 提供了显着的优势,但它也引入了开发人员需要理解的新概念和模式。以下是一个分步指南,可帮助您完成迁移过程:

  1. 了解关键差异: 在开始迁移之前,请确保您彻底理解 Pages Directory 和 App Directory 之间的关键差异,包括路由系统、数据获取和组件架构。
  2. 创建一个 `app` 目录: 在您的 Next.js 项目的根目录中创建一个名为 `app` 的新目录。此目录将包含作为 App Directory 一部分的所有组件和路由。
  3. 逐步迁移路由: 一次递增地迁移路由,一次一个。这将允许您单独测试和调试每个路由,从而最大限度地降低引入错误的风险。
  4. 将组件转换为服务器组件: 尽可能将现有的 React 组件转换为服务器组件。这将提高性能并减少需要在浏览器中下载和执行的 JavaScript 数量。
  5. 更新数据获取逻辑: 更新您的数据获取逻辑以利用 App Directory 的内置数据获取功能。这可能涉及将数据获取代码从客户端组件移动到服务器组件。
  6. 实施布局和模板: 实施布局和模板以定义跨多个页面一致的共享 UI 元素。
  7. 彻底测试: 彻底测试每个迁移的路由,以确保其功能正确且不存在任何回归。
  8. 删除 `pages` 目录: 迁移所有路由后,您可以删除 `/pages` 目录。

结论

Next.js App Directory 代表着基于文件的路由的重大演变,为开发人员提供了一种更有组织、性能更好且更灵活的方式来构建现代 Web 应用程序。通过理解关键概念并接受新功能,开发人员可以利用 App Directory 来创建卓越的用户体验并实现更高的生产力。Next.js 开发的未来在于 App Directory,并且采用它是构建尖端 Web 应用程序的战略举措。它是全球开发人员的强大工具。

随着 Next.js 生态系统的不断发展,App Directory 有望成为构建强大、可扩展且高性能 Web 应用程序的标准。拥抱改变,探索可能性,并释放 Next.js 的全部潜力!