如何使用Next.js创建全栈应用程序

Next.js 乍一看似乎令人生畏,因为有这么多新概念需要掌握。但别担心——在这个循序渐进的教程中,我将为您提供使用 Next.js 创建您的第一个现代全栈应用程序所需的所有基本信息。

在本教程中,我将带您了解 Next.js 的基础知识,并指导您创建您的第一个全栈应用程序。在本教程结束时,您将有信心开始使用 Next.js 构建您自己的全栈应用程序。

因此,让我们直接进入并一起释放 Next.js 的力量。

(NextJs教程:java567.com/search.html?sWord=next&v=2306015)

以下是我们将介绍的内容:

  • 我们要建造什么?
  • 入门
  • 如何在 Next.js 中创建共享布局
  • 如何在 Next.js 中创建自定义导航栏
  • 如何在 Next.js 中创建 API 路由
  • 如何建立主页
  • Next.js 中的 App Router 是什么?
  • 如何增强 Next.js 代码库的模块化和可维护性
  • 如何创建动态角色页面
  • 如何在 Next.js 中创建动态 API 路由
  • 如何在 Next.js 中创建动态 UI 路由
  • Next.js 中有什么generateStaticParams
  • dynamicParamsNext.js 生成静态页面的目的是什么?
  • 如何生成静态页面generateStaticParams
  • 如何建立测验部分
  • 如何在 Next.js 中创建客户端组件
  • 结论

好吧,让我们开始吧!

我们要建造什么?

在本教程中,我们将创建一个引人入胜的应用程序来展示有关恶搞之家角色的信息。此外,我们将包括一个测验部分,用户可以在其中测试他们对节目的了解。

为了让您简单熟悉,我们将避免使用数据库,而是使用本地 JSON 数据。通过消除数据库集成的复杂性,我们可以专注于掌握 Next.js 的基本概念。

Nex.js 应用程序正在显示有关家庭成员角色的数据申请预览

入门

要开始学习本教程,我强烈建议使用我专门为本教程创建的入门样板。它已经包括必要的依赖项和文件夹结构,从而节省了您宝贵的时间,无需从头开始设置您的项目。

只需从 GitHub 存储库中克隆入门样板,然后按照教程进行操作。这样,您就可以专注于学习和实施这些概念,而不会陷入设置细节中。

设置启动样板并在本地计算机上成功运行后,您应该能够看到初始页面。此页面标志着我们教程的开始,并将作为我们旅程的起点。

样板的初始页面样板的初始页面

从这里开始,我们将逐步构建现有代码并在我们的应用程序中实现一些很酷的功能。让我们开始吧,马上开始吧!

如何在 Next.js 中创建共享布局

通常在您的应用程序中,您有跨多个页面共享的元素,例如导航栏或页脚。手动将这些元素添加到每个页面可能既乏味又容易出错。幸运的是,Next.js 提供了一种便捷的方式来创建可在整个应用程序中重复使用的共享布局。

第一种布局称为根布局。顾名思义,此布局在我们应用程序的所有页面之间共享。它作为最顶层的布局,为我们的整个应用程序提供了一致的结构。Root Layout 是必需的,我们需要确保它包含必要的 HTML 和 body 标签。

接下来,让我们考虑应用程序中的各个路由段。每个段都可以选择定义自己的布局。这些布局类似于根布局,将在该段内的所有页面之间共享。这允许您为应用程序的不同部分设置特定的布局,同时仍然在每个部分中保持一致的结构。

现在,打开app/layout.js并向其中添加以下代码:

 // ? app/layout.js
 ​













 const inter = Inter({ subsets: ['latin'] })
 ​




 export const metadata = {
   title: 'Family Guy',
   description: 'Come here and learn more about Family Guy!',
 }
 ​



 export default function RootLayout({ children }) {
   return (
     <html lang="en">
       <body className={inter.className}>
         <Navigation />
         {children}
       </body>
     </html>
   )
 }

您在这里看到的组件是 Root Layout 组件,它在为整个应用程序创建共享布局方面起着至关重要的作用。让我们仔细看看它的结构和功能。

在组件中,您定义metadata对象,其中包含应用程序的默认元数据标签。该title属性指定您的应用程序的标题,而该description属性提供简短的描述。这些元数据标签对于搜索引擎优化 (SEO) 很重要,如果需要可以针对特定路线覆盖这些标签。

RootLayout函数内部,您可以使用htmlbody标记构建 HTML 文档。您将标记lang的属性设置html"en"以指示内容是英文的。

body标记中,您包括Navigation从目录导入的组件components。该组件代表您的导航栏,并将在您的应用程序的所有页面之间共享。通过将其包含在此处,您可以确保它在整个应用程序中始终如一地显示。

propchildren是一个特殊的 prop,代表组件内呈现的内容RootLayout。这允许您在共享布局中嵌套其他组件和内容。

最后,您导出RootLayout组件,使其可用于整个应用程序。

如何在 Next.js 中创建自定义导航栏

在本节中,您将为您的应用程序创建一个简单的导航栏组件。导航栏将包含一个徽标和一个将用户带到测验部分的链接。打开components/Navigation.jsx并添加以下代码:

 // ? components/Navigation.jsx
 ​













 export const Navigation = () => {
   return (
     <div className="sticky top-0 backdrop-blur-xl bg-[rgba(0,0,0,0.8)] border-b border-slate-800 z-50">
       <Container className="flex justify-between py-5">
         <Link href="/">
           <Image src="/logo.png" alt="Family Guy" width={70} height={50} />
         </Link>
         <Link
           href="/quiz"
           className="flex items-center justify-center gap-1 px-5 font-semibold text-black transition-colors bg-green-500 rounded-md duration-600 hover:bg-green-600"
         >
           <TbArrowBigRightFilled className="text-lg" />
           Take a Quiz
         </Link>
       </Container>
     </div>
   )
 }

现在你有了Navigation在整个应用程序中共享的粘性组件。如果你打开你的本地服务器,你应该能够看到以下结果:

粘性导航栏预览粘性导航栏预览

祝贺你到目前为止的进步!您已经为您的 Next.js 应用程序成功创建了一个带有导航栏的共享布局。这种共享布局可确保所有页面的一致性,从而更轻松地管理整个应用程序中的导航栏等元素。

现在,是时候专注于构建主页来显示字符了。为了在主页上显示这些字符,您需要创建一个 API 路由来从您的本地 JSON 文件中检索所有字符,从而允许您使用相关信息动态填充主页。

如何在 Next.js 中创建 API 路由

Next.js 中的路由是一个基本概念,它决定了如何访问应用程序的不同部分。当你在 Next.js 的目录中创建一个文件夹时app,它会自动成为一个路由。但是您可以灵活地定义它应该是 UI 路由还是 API 路由。

命名路由文件夹中的文件page.jsx会将其转换为 UI 路由。这意味着它将充当带有 UI 组件的常规页面。另一方面,如果你将文件命名为route.js,它就变成了一个 API 路由。这表示它将处理 API 请求和响应。

请务必记住,在单个目录中,您可以拥有 UI 路由或 API 路由,但不能同时拥有两者。这种清晰的分离允许在构建 Next.js 应用程序时有一个干净和有组织的结构。

在下一节中,您将在 Next.js 中创建您的第一个 API 路由。Next.js 中的 API 路由提供了一种在应用程序中创建服务器端端点的简单方便的方法。

使用 API 路由,您可以定义处理 HTTP 请求和响应的自定义路由,允许您获取或修改数据、执行服务器端计算或与外部服务集成。

这些路由被编写为 JavaScript 函数,在云中自动部署为无服务器函数。API 路由在您的前端 Next.js 应用程序中提供类似后端的功能,使您能够构建动态和交互式 Web 应用程序,而无需单独的服务器。

如何建立主页

在本节中,您将创建一个 API 路由,使您能够检索存储在本地 JSON 文件中的所有可用字符。通过实施此 API 路由,您将能够获取字符并将其显示在应用程序的主页上。

如何创建字符 API 路由

为了确保 API 代码和 UI 代码之间的清晰分离,您将在目录中放置所有 API 路由app/api

通过采用这种方法,您可以有效地将与 API 相关的功能与用户界面隔离开来,从而促进更好的组织和可维护性。

本节将指导您完成创建 Characters API 路由的过程。只需打开app/api/characters/route.js文件并添加以下代码:

 // ? app/api/characters/route.js
 ​













 export async function GET() {

   return NextResponse.json({ characters: characters.data })
 }

在此代码片段中,您将导入一个名为 .json 的 JSON 文件characters.json。该文件包含有关您要在应用程序中使用的字符的数据。

NextResponse接下来,您将从模块中导入对象next/server。此对象提供用于处理 Next.js 应用程序中的服务器响应的函数。

之后,您定义一个名为 的异步函数GET()。此函数与 HTTP GET 请求方法相关联,该方法通常用于从服务器检索数据。

在函数内部GET(),您使用该NextResponse.json()函数来构造服务器响应。您传递一个具有名为 的属性的对象characters,该对象保存文件中的数据characters.json。然后从函数返回此响应。

简而言之,这段代码正在创建一个响应 GET 请求的 API 路由。当向该路由发出 GET 请求时,它会返回一个包含文件数据的 JSON 响应characters.json。这允许您从您的应用程序中获取字符数据并将其用于我们代码的其他部分。

现在,是时候测试您的 API 路由并确保一切正常运行了。为了简化此过程,您将使用浏览器本身来发出 API 请求。打开浏览器并输入以下 URL:http://localhost:3000/api/characters

执行此操作后,您将被定向到一个页面,您可以在其中观察 API 请求的结果。这一步允许我们验证 API 路由是否按预期工作并且它是否成功获取字符数据:

浏览器中的 JSON 数据浏览器中的 JSON 数据

这是包含字符列表的 JSON 数据。如果 JSON 数据在您的浏览器中看起来很奇怪,请确保在您的浏览器上安装 JSON Formatter 扩展。我使用的是谷歌浏览器,所以我在我的浏览器上使用了这个 JSON Formatter。

如何在首页显示字符

现在您已经设置了 API,让我们为我们的主页创建 UI 并显示字符。为此,打开app/page.jsx文件并添加以下代码片段:

 // ? app/page.jsx

 ​













 async function getAllCharacters() {
   const data = await fetch(`${endpoint}/characters`)
 ​


   if (!data.ok) {


     throw new Error('Failed to fetch data')


   }


 ​



   return data.json()


 }


 ​




 export default async function Page() {

   const data = await getAllCharacters()
 ​

   return (

     <main>
       <Container className="grid grid-cols-2 gap-1 py-5 md:grid-cols-3 lg:grid-cols-4">
         {data?.characters?.map(item => {
           return (
             <Link
               href={`/characters/${item.slug}`}
               key={item.name}
               className="overflow-hidden rounded-md"
             >
               <Image
                 src={item.avatar}
                 
                 className="transition-all duration-500 hover:scale-110 hover:-rotate-2"
                 width={500}
                 height={500}
               />
             </Link>
           )
         })}
       </Container>
     </main>
   )
 }

在上面的代码片段中,您有一个名为 React 的组件Page,它被定义为一个异步函数。该组件负责渲染首页 UI。

首先,您调用了一个异步函数getAllCharacters,该函数使用“fetch”函数向 API 端点发出异步 HTTP 请求。此请求的响应存储在data变量中。

接下来,您将进行错误处理检查。如果 HTTP 响应返回错误(状态代码不是 200),我们将抛出一个错误,表明数据获取失败。

转到组件Page,它等待调用函数的结果getAllCharacters 。结果数据存储在data变量中。

return 语句呈现主页的 UI。它使用一个main标签作为顶级容器和一个Container组件来保存具有多列的网格布局。

在 中Container,您映射对象characters中的数组data并生成项目列表。对于每个角色,我们创建一个“链接”组件,用作指向特定角色页面的可点击链接。链接的 URL 是根据角色的 slug 属性生成的。

在 中Link,您有一个Image显示角色头像图像的组件。

总的来说,此代码从 API 端点获取数据,特别是字符数据。然后,它使用此数据动态呈现角色化身的网格布局,并带有指向各个角色页面的可点击链接。

应用程序的首页显示了家庭成员角色列表主页

您的主页现在看起来很棒,但您可能已经注意到我们获取数据的方式有些不寻常。通常,您可能熟悉使用 useEffect 挂钩从 API 获取数据。但在这种情况下,您没有使用任何钩子——但您的代码运行良好。

在下一节中,我们将仔细研究这个组件中究竟发生了什么。通过检查代码及其执行,您将对 Next.js 机制有更深入的了解。

Next.js 中的 App Router 是什么?

Next.js 中的 App Router 引入了一种利用 React 的最新功能开发应用程序的新范例。如果您已经熟悉 Next.js,您会发现 App Router 代表了基于文件系统的现有 Pages Router 的自然演变。

默认情况下,App Router 基本上使您能够在服务器上运行 React 代码,因此您在服务器上获取数据并且只将静态 HTML 返回给客户端。这意味着我们有一个服务器组件,它从服务器检索数据并在服务器端呈现其内容。

有一个需要考虑的警告:您将无法访问服务器组件中的 React 状态和 React Hooks 等客户端功能,因为它们仅在服务器上运行。

如果要使用客户端功能,则必须通过"use client"在文件顶部添加来在组件文件中指定它。

Next.js 中服务器端渲染的意义何在?

在 Next.js 中,SSR 允许服务器生成网页的 HTML 内容并将其发送到浏览器。这意味着当您访问 Next.js 网站时,您不必等待 JavaScript 代码在浏览器上加载和执行就可以看到任何内容。相反,服务器会发送一个预呈现的 HTML 页面,该页面几乎可以立即显示。

SSR 的优势在于它可以缩短网页的初始加载时间,提供更快、更无缝的用户体验。它还有助于搜索引擎优化 (SEO),因为搜索引擎可以轻松抓取和索引服务器呈现的 HTML 内容。

Next.js 中的服务器端渲染方法

Next.js 提供了几种渲染页面的方法。这些方法中的每一个都有特定的用途,并且可以在不同的场景中使用:

  • 静态站点生成 (SSG): 静态生成是一种服务器端呈现方法,其中 Next.js 在构建时生成 HTML。在构建过程中,Next.js 从 API 或其他数据源获取数据并预呈现 HTML 页面。然后可以根据请求将这些预呈现的页面提供给客户端。SSG 适用于内容不经常变化的网站。
  • 服务器端渲染 (SSR): 服务器端渲染是 Next.js 在每个请求上生成 HTML 的另一种方法。当用户访问页面时,Next.js 获取数据并在服务器上呈现 HTML,然后再将其发送到客户端。SSR 对于内容更新频繁或用户体验个性化的网站很有用。
  • 增量静态重新生成 (ISR): ISR 是 Next.js 中的一项功能,它允许您按需静态生成页面,而不是在构建时。这意味着您的站点可以同时静态生成和动态生成。

现在我们对 Next.js 中的服务器端渲染有了更好的理解,我们可以继续下一节。

如何增强 Next.js 代码库的模块化和可维护性

为了避免代码重复并增强代码的可重用性,您可以在 Next.js 项目中采用模块化的方法。通过将常用功能隔离getAllCharacters在一个单独的模块中,您可以方便地在代码库的多个部分访问和重用它们。

您可以在项目中进行快速调整。首先,导航到app/page.jsx文件并找到getAllCharacters顶部的函数。从文件中删除这个函数。

接下来,打开文件并从那里lib/characters.js导出函数。getAllCharacters通过将函数移动到单独的模块,您可以轻松地在代码库的不同部分导入和使用它:

 // ? lib/characters.js
 ​













 import { endpoint } from '@/utils/endpoint'
 ​




 export async function getAllCharacters() {
   const data = await fetch(`${endpoint}/characters`)
 ​

   if (!data.ok) {
     throw new Error('Failed to fetch data')
   }
 ​


   return data.json()
 }

现在让我们getAllCharacters从中导入函数lib/characters.js并在内部使用它app/page.jsx

 // ? app/page.jsx

 ​













 import { getAllCharacters } from '@/lib/characters'

 ​




 export default async function Page() {
   const data = await getAllCharacters()
 ​

   return (
     <main>
        //content went here ...
     </main>
   )
 }

这样,您将可以在整个代码库中访问此获取功能。

如何创建动态角色页面

恭喜您在本教程中达到了这一点!到目前为止,您已经对 Next.js 的基础知识有了深入的了解。

在本节中,您将创建一个动态 API 路由。此路线将使您能够单独获取每个角色的数据,并随后构建一个用户界面 (UI) 以向您的用户展示这些角色。

如何在 Next.js 中创建动态 API 路由

通过在 Next.js 中创建动态 API 路由,您可以根据角色的 slug 获取角色数据。为此,您需要使用方括号来命名您的文件夹,向 Next.js 表明这是一个动态路由。通过相应地命名文件夹,您可以在代码中访问此动态值,从而允许您检索和显示所需角色的数据。

打开api/characters/[slug]/route.js并添加以下代码段:

 // ? api/characters/[slug]/route.js 
 ​













 export async function GET(req, { params }) {


   try {



     const character = characters.data.find(item => item.slug === params.slug)
 ​



     if (!character) {
       return new NextResponse('not found', { status: 404 })


     }


 ​


     const character_qoutes = qoutes.data.filter(
       item => item.character_id === character.id,
     )
 ​

     return NextResponse.json({
       character,
       character_qoutes: character_qoutes.length > 0 ? character_qoutes : null,
     })
   } catch (error) {
     return new NextResponse('Internal Server Error', { status: 500 })
   }
 }

在上面的代码片段中,您有一个名为GET处理 Next.js API 路由中的 GET 请求的异步函数。让我们一步一步地分解它:

  1. 您使用 Next.js 文件系统(和)从它们各自的 JSON 文件中导入characters和数据。quotes``@/data/characters.json``@/data/quotes.json
  2. 该函数接收两个参数:(req代表传入的请求)和一个对象params,该对象包含从请求 URL 中提取的动态参数。
  3. characters在 try-catch 块内,代码尝试通过将slug参数 fromparams与每个字符对象的属性进行比较来查找数据中的字符slug
  4. 如果未找到任何字符,代码将使用包NextResponse中的类返回状态代码为 404 的“未找到”响应next/server
  5. 如果找到一个字符,代码将继续根据quotescharacter_id找到的字符的id.
  6. 过滤后的字符引号分配给character_quotes变量。
  7. 最后,代码使用 返回一个 JSON 响应NextResponse.json(),包括character对象和character_quotes数组(或者null如果没有找到引号)。

Next.js 自动从 URL 中提取动态参数并使它们在params对象中可用。在此代码中,您slug使用访问参数params.slug。这允许您从 URL 中检索特定字符的 slug,并使用它来查找数据中的相应字符characters

现在您可以测试此端点以查看结果,在浏览器中打开http://localhost:3000/api/characters/peter-griffin,您应该能够看到以下 JSON 数据:

浏览器中的 JSON 数据浏览器中的 JSON 数据

如何在 Next.js 中创建动态 UI 路由

现在您的 API 已设置并能够获取角色数据,是时候创建一个动态 UI 页面来展示此数据了。

创建动态 UI 页面的过程与您在设置动态 API 路由时所做的非常相似。但这一次,您将使用page.jsx而不是route.js生成 UI 路由。

打开app/characters/[slug]/page.jsx并添加以下代码段:

 // ? app/characters/[slug]/page.jsx
 ​













 import { getAllCharacters } from '@/lib/characters'

 ​




 export const dynamicParams = false
 ​



 export async function generateStaticParams() {
   const { characters } = await getAllCharacters()
   return characters.map(character => ({ slug: character.slug }))
 }
 ​


 export async function getCharacterBySlug(slug) {
   const data = await fetch(`${endpoint}/characters/${slug}`)
 ​

   if (!data.ok) {
     throw new Error('Failed to fetch data')
   }
 ​
   return data.json()
 }

 ​
 export default async function Page({ params }) {
   const { character, character_qoutes } = await getCharacterBySlug(params.slug)
 ​
   return (
     <Container className="flex flex-col gap-5 py-5" as="main">
       <div className="flex flex-col gap-2">
         <h1 className="text-2xl font-semibold capitalize">{character.name}</h1>
         <ul className="flex gap-1 text-sm">
           {character.occupations.map(item => {
             return (
               <li
                 key={item}
                 className="p-2 text-gray-300 bg-gray-800 rounded-md"
               >
                 {item}
               </li>
             )
           })}
         </ul>
       </div>
       <p className="text-sm leading-6">{character.description}</p>
       <ul className="grid gap-2 sm:grid-cols-2">
         {character.images.map(image => {
           return (
             <li
               key={image}
               className="relative flex overflow-hidden bg-gray-900 rounded-xl"
             >
               <Image
                 className="transition-all duration-500 hover:scale-110 hover:rotate-2"
                 src={image}
                 
                 width={760}
                 height={435}
               />
             </li>
           )
         })}
       </ul>
       {character.skills && (
         <>
           <h2 className="text-xl font-bold">Power and Skills</h2>
           <ul className="flex flex-wrap gap-1">
             {character.skills.map(item => {
               return (
                 <li
                   className="flex justify-center flex-grow px-2 py-1 text-orange-400 rounded-full bg-orange-950"
                   key={item}
                 >
                   {item}
                 </li>
               )
             })}
           </ul>
         </>
       )}
       {character_qoutes && (
         <>
           <h2 className="text-xl font-bold">Famous Qoutes</h2>
           <ul className="grid gap-5">
             {character_qoutes.map((item, idx) => {
               return (
                 <li
                   className="p-2 italic text-gray-400 border-l-4 border-green-400 rounded-md"
                   key={item.idx}
                 >
                   {item.qoute}
                 </li>
               )
             })}
           </ul>
         </>
       )}
     </Container>
   )
 }

不要被您在这里看到的代码的长度吓到!乍一看,这似乎势不可挡,但实际上非常简单。让我们更深入地看看我们在这段代码中做了什么:

Next.js 中有什么generateStaticParams

在 Next.js 中,该generateStaticParams函数用于指定应在构建时预渲染的动态路由。

为了更简单地解释它,让我们假设您有一个包含多篇博文的网站,并且每篇博文都有一个唯一的 URL。使用generateStaticParams,您可以告诉 Next.js 在构建过程中应该生成和预呈现哪些博客文章 URL。

当您实现 时generateStaticParams,您为其提供一个函数,该函数返回一个对象数组,表示您要预呈现的动态路径。

每个对象通常包含一个与 URL 的动态部分相对应的参数。例如,如果您的博客文章具有/blog/post-1/blog/post-2等 URL,您将返回一个包含{ params: { slug: 'post-1' } }{ params: { slug: 'post-2' } }等对象的数组。

在我们的例子中,我们正在使用该函数检索字符列表getAllCharacters()。然后我们映射字符并返回一个对象数组,每个对象都包含一个slug带有字符 slug 值的属性。

Next.js 然后将使用此信息在构建过程中为这些路径生成静态 HTML 文件。这允许页面作为静态文件提供,从而提高性能和 SEO。

dynamicParamsNext.js 生成静态页面的目的是什么?

在 Next.js 中,未使用生成的动态片段的行为generateStaticParamsdynamicParams.

dynamicParams设置为 时true,Next.js 将在访问动态段时尝试动态获取相应的页面。

另一方面,如果dynamicParams设置为false,Next.js 将在找不到请求的页面时返回 404 页面。

此设置允许您定义 Next.js 如何处理未预先生成的动态段,从而在处理应用程序中的动态路由时提供灵活性。

如何生成静态页面generateStaticParams

现在您已经成功地为每个角色生成了静态路径,让我们看看如何为每个角色获取数据。

getCharacterBySlug函数是一个异步函数,它接受slug参数,使用从指定的 API 端点获取数据fetch,并返回 JSON 格式的响应数据。如果响应不成功 ( !data.ok),则会抛出错误。

组件将对象作为 propPage接收,其中包含从 URL 中提取的动态参数值。params它调用该getCharacterBySlug函数,传递从中提取的角色的 slugparams以获取特定角色的数据。

返回的数据然后用于填充 UI,其中包括显示角色的名称、职业、描述、图像、力量和技能(如果可用)以及名言(如果可用)。

理想情况下,您可以将其放入getCharacterBySlug内部lib/characters.js并从那里导出,但这由您决定!

具有动态路线的 Family Guy 应用程序到目前为止我们的应用

如何建立测验部分

恭喜您在本教程中达到了这一点!通过创建动态 API 路由和 UI 页面,并了解 Next.js 中的不同呈现方法,您已经取得了很多成就。

现在,让我们为这个应用程序添加一些交互性。在本节中,您将构建一个引人入胜的测验部分,用户可以在其中测试他们的 Family Guy 知识。

如何创建 API 路由以检索随机问题

为确保每个用户都能获得令人兴奋和独特的体验,重要的是要避免每次都在测验中重复相同的问题。我们希望保持新鲜感和吸引力。

为了实现这一点,我们将实施一种机制,在用户每次开始测验时向他们提出不同的问题。

打开app/api/quiz/random/route.js并添加以下代码段:

 // ? app/api/quiz/random/route.js
 ​













 export async function GET() {

   try {



     const random = Math.floor(Math.random() * questions.data.length)
     return NextResponse.json({
       randomQuestion: questions.data[random].id,
     })
   } catch (error) {
     return new NextResponse('Internal Server Error', { status: 500 })
   }
 }

在此 Next.js API 路由中,您将实现从存储在名为 .json 文件中的一组问题中提取随机问题的逻辑quiz.jsonquestions首先,我们从 JSON 文件中导入数据,NextResponse从 Next.js 服务器包中导入对象。

在函数内部GET,我们使用Math.random()Math.floor()函数生成一个随机数。该数字用于从questions.data数组中选择一个随机问题。我们使用其索引检索问题,特别是id随机选择问题的属性。

现在让我们创建一个 UI 来使用这个随机问题。

如何为测验创建介绍页面

您现在将为测验介绍部分创建一个用户界面 (UI)。此 UI 将作为用户在开始测验之前看到的初始屏幕。

您将利用刚刚创建的 API 路由在用户每次开始测验时动态地将用户重定向到一个新问题。

让我们打开app/quiz/page.jsx并添加以下代码:

 // ? app/quiz/page.jsx
 ​













 export async function getRandomQuizQuestion() {
   const data = await fetch(`${endpoint}/quiz/random`, { cache: 'no-store' })
 ​


   if (!data.ok) {


     throw new Error('Failed to fetch data')


   }


 ​



   return data.json()


 }


 ​




 export default async function Page() {

   const data = await getRandomQuizQuestion()
 ​

   return (

     <Container
       as="main"
       className="flex flex-col gap-5 py-5 md:flex-row-reverse md:justify-between"
     >
       <div className="relative overflow-hidden rounded-2xl">
         <div className="md:w-[24rem]">
           <Image src="/wallpaper.jpg"  width={700} height={700} />
         </div>
         <div className="absolute top-0 bottom-0 left-0 right-0 bg-gradient-to-t from-black to-transparent md:bg-gradient-to-r"></div>
       </div>
 ​
       <div className="md:w-[50%] flex flex-col gap-5">
         <h1 className="text-2xl font-semibold">Family Guy Quiz</h1>
         <p className="text-sm leading-6 text-gray-300">
           Take this quiz to find out how much you know about the hit animated
           sitcom Family Guy. Test your knowledge of the characters, the
           episodes, and the show&apos;s many pop culture references.
         </p>
         <Link
           href={`/quiz/${data.randomQuestion}`}
           className="flex items-center justify-center gap-1 px-5 py-4 font-semibold text-orange-500 transition-colors rounded-md outline duration-600 hover:bg-orange-950"
         >
           <TbArrowBigRightFilled className="text-lg" />
           Take a Quiz Now!
         </Link>
       </div>
     </Container>
   )
 }

此代码为测验介绍部分设置 UI,从 API 中获取随机问题,并为用户提供开始测验的按钮。

在此代码中,您可能已经注意到我们将参数传递给方法的变化fetch{ cache: 'no-store' }

此更改意义重大,因为它确保我们正在处理的页面不会使用静态站点生成 (SSG) 方法静态生成。相反,它会向提供的端点发出 API 请求,并在用户每次访问该页面时获取新数据。

通过使用{ cache: 'no-store' },我们禁用对此特定请求的缓存。这保证每次用户访问此页面时,都会获取一个新问题。

这种方法为测验体验添加了动态和交互元素,确保用户每次访问页面时总是遇到不同的问题。

家庭成员测验部分的介绍页面测验介绍页面

如何为测验问题创建动态 API 路由

要提供动态测验问题,您需要创建一个新的 API 路由来获取并返回测验问题。这样,您可以动态检索问题并将其呈现给用户。

打开app/api/quiz/[id]并添加以下代码:

 // ? app/api/quiz/[id]
 ​













 export async function GET(req, { params }) {


   try {



     const question = questions.data.find(item => item.id === params.id)

 ​



     if (!question) {

       return new NextResponse('not found', { status: 404 })


     }


 ​


     const { correct_answer, ...rest } = question
 ​




     return NextResponse.json({
       question: rest,
     })
   } catch (error) {
     return new NextResponse('Internal Server Error', { status: 500 })
   }
 }

在此 Next.js 路由中,您处理 GET 请求以从测验中检索特定问题。您questions从 JSON 文件导入数据。使用请求参数中提供的 ID,您可以搜索匹配的问题。如果未找到问题,您将返回状态代码为 404 的“未找到”响应。

如果找到问题,则提取正确答案并将剩余的问题详细信息存储在rest变量中。

最后,您返回包含问题详细信息(不包括正确答案)的 JSON 响应。如果在此过程中发生任何错误,您将返回状态代码为 500 的“内部服务器错误”响应。

现在,您可以通过打开本地服务器http://localhost:3000/api/quiz/CfQnf3lH56在浏览器中测试此 API 路由:

图片94http://localhost:3000/api/quiz/CfQnf3lH56

如何创建动态 API 路由以获取答案

在实现 UI 之前,让我们创建一个 API 路由,您可以在其中获取每个问题的正确答案。

打开app/api/quiz/answer/[id]/route.js并向其中添加以下代码:

 // ? app/api/quiz/answer/[id]/route.js
 ​













 export async function GET(req, { params }) {


   try {



     const question = questions.data.find(item => item.id === params.id)

 ​



     if (!question) {

       return new NextResponse('not found', { status: 404 })


     }


 ​


     const { correct_answer } = question
 ​




     const filteredQuestions = questions.data.filter(
       item => item.id !== params.id,
     )
     const random = Math.floor(Math.random() * filteredQuestions.length)
 ​
     return NextResponse.json({
       correct: correct_answer,
       random: filteredQuestions[random].id,
     })
   } catch (error) {
     return new NextResponse('Internal Server Error', { status: 500 })
   }
 }

此 API 路由的目的是根据提供的 ID 从测验中检索特定问题。该代码通过将给定 ID 与存储在数据中的问题 ID 进行比较来搜索问题questions。如果找到请求的问题,则提取其正确答案。

为了建议下一个问题,代码通过过滤将当前问题从可用问题池中移除。然后它会在剩余问题的范围内生成一个随机索引。使用这个随机索引,选择一个新问题作为下一个问题的建议。

该代码构建并返回一个 JSON 响应,其中包含所请求问题的正确答案和随机选择的下一个问题的 ID。此功能允许用户检索特定的测验问题并接收对以下问题的建议,从而改善测验的交互体验。

如何为测验问题创建动态 UI 路由

现在您已经成功构建了所有必要的 API 端点,是时候采取下一步并创建一个用户界面 (UI) 以允许用户与您开发的 API 交互。此 UI 将作为用户访问和使用您的 API 提供的功能的网关。

在本节中,您将了解 Next.js 中的动态服务器端渲染 (SSR)。我们已经介绍了静态页面 (SSG),现在您将探索 SSR。SSR 也更容易实施。

打开app/quiz/[id]/page.jsx并添加以下代码:

 // ? app/quiz/[id]/page.jsx
 ​













 async function getQuizQuestion(id) {
   const data = await fetch(`${endpoint}/quiz/${id}`)
 ​


   if (!data.ok) {


     throw new Error('Failed to fetch data')


   }


 ​



   return data.json()


 }


 ​




 ​
 export default async function Page({ params }) {
   const { question } = await getQuizQuestion(params.id)
 ​

   return (
     <Container as="main" className="flex flex-col gap-5 py-5">
       <h1 className="text-lg font-semibold">{question.title}</h1>
       <Answer answers={question.answers} questionId={params.id} />
     </Container>
   )
 }

此 Next.js 页面组件使用该函数从 API 端点获取问题数据getQuizQuestion。然后它使用 JSX 组件呈现问题的标题和相应的答案。

显示动态问题的 Next.js UI 路由道路用户界面问题

这就是渲染 Next.js 页面服务器端所需要做的一切!

在下一节中,您将创建一个客户端组件来处理用户与答案的交互。

如何在 Next.js 中创建客户端组件

在本教程的最后一部分,您将创建一个客户端组件来处理用户与答案的交互。

打开components/Answer.jsx并添加以下代码:

 // ? components/Answer.jsx
 ​













 'use client'
 ​




 import { useEffect, useState } from 'react'
 import cn from 'classnames'
 import Link from 'next/link'
 import { FiRepeat } from 'react-icons/fi'
 import { MdNearbyError } from 'react-icons/md'
 import { FaCheck } from 'react-icons/fa'
 ​


 export const Answer = ({ answers, questionId }) => {
   const [selected, setSeleceted] = useState(null)
   const [data, setData] = useState(null)
   const [loading, setLoading] = useState(false)
 ​

   useEffect(() => {
     let subscribed = true
     if (selected) {
       setLoading(true)
       fetch(`/api/quiz/answer/${questionId}`)
         .then(res => res.json())
         .then(data => {
           setLoading(false)
           if (subscribed) {
             setData(data)
           }
         })
     }
 ​
     return () => {
       console.log('cancelled!')
       subscribed = false
     }
   }, [questionId, selected])
 ​
   return (
     <>
       <ul className="grid grid-cols-2 gap-2 md:grid-cols-4">
         {answers.map(item => {
           const isLoading = selected === item && loading
           const isWrong =
             selected === item && data && data?.correct !== selected
           const isCorrect = data?.correct === item
 ​
           return (
             <li key={item}>
               <button
                 disabled={data || loading}
                 onClick={() => setSeleceted(item)}
                 className={cn(
                   'p-2 rounded-md  items-center justify-between w-full flex text-sm font-semibold disabled:cursor-not-allowed transition-all',
                   isLoading && 'animate-pulse',
                   isWrong ? 'bg-red-700' : 'bg-slate-800',
                   isCorrect && 'outline text-green-500',
                 )}
               >
                 {item}
                 {isCorrect && <FaCheck />}
                 {isWrong && <MdNearbyError />}
               </button>
             </li>
           )
         })}
       </ul>
       {data?.random && (
         <Link
           href={`/quiz/${data.random}`}
           className="flex items-center gap-1 text-blue-400"
         >
           <FiRepeat className="mt-1" />
           Do it again
         </Link>
       )}
     </>
   )
 }

这是一个带有两个道具的 React 组件:answersquestionId。它使用挂钩设置状态useState以跟踪所选答案、获取的数据和加载状态。

在组件内部,有一个钩子在or值更改useEffect时运行。如果存在答案,它会发出 API 请求以获取相应的数据并相应地更新状态。questionId``selected``selected``fetch

该组件使用映射函数呈现答案选项列表。每个答案选项都表示为一个按钮。按钮的外观会根据所选答案、加载状态和答案的正确性进行修改。它还会根据所选答案的正确性显示不同的图标,例如复选标记或错误图标。

此外,如果获取的数据包含random属性,则会呈现一个链接以使用新的随机问题重复测验。

这是我们测验的最终版本的样子:

测验最终版本测验最终版本

结论

这是结束!您已经使用 Next.js 成功构建了您的第一个全栈应用程序。在本分步教程中,您学习了 Next.js 的基础知识,探索了其强大的功能并获得了创建现代 Web 应用程序所需的知识。

通过本教程,您不仅构建了一个功能性应用程序,而且还获得了开始使用 Next.js 创建我们自己的全栈应用程序的信心。您了解了路由、服务器端呈现、API 集成等。

现在您在 Next.js 中有了坚实的基础,可能性是无限的。您可以继续探索高级主题,例如数据库集成、身份验证和部署,以使您的应用程序更上一层楼。

(NextJs教程:java567.com/search.html?sWord=next&v=2306015)

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MY8ZTmoY' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片