NodeJS系列(11)- Next.js 框架 (四) | 数据获取(Data Fetching)
<p><br>在 “NodeJS系列(8)- Next.js 框架 (一) | 安装配置、路由(Routing)、页面布局(Layout)” 里,我们简单介绍了 Next.js 的安装配置,创建了 nextjs-demo 项目,讲解和演示了 Next.js 项目的运行、路由(Routing)、页面布局(Layout)等内容。<br><br>在 “NodeJS系列(9)- Next.js 框架 (二) | 国际化 (i18n)、中间件 (Middleware)” 里,我们在 nextjs-demo 项目基础上,讲解和演示了 Next.js 项目的国际化 (i18n)、中间件 (Middleware) 等内容。<br><br>在 “NodeJS系列(10)- Next.js 框架 (三 ) | 渲染(Rendering)” 里,我们在 nextjs-demo 项目基础上,讲解和演示了渲染(Rendering)。<br><br>本文继续在 nextjs-demo 项目(Pages Router)基础上,讲解和演示数据获取(Data Fetching)。<br><br>NextJS: https://nextjs.org/<br>NextJS GitHub: https://github.com/vercel/next.js</p><p> </p>
<h3>1. 系统环境</h3>
<p> 操作系统:CentOS 7.9 (x64)<br> NodeJS: 16.20.0<br> NPM: 8.19.4<br> NVM: 0.39.2<br> NextJS: 13.4.12</p>
<p> </p>
<h3>2. 数据获取 (Data Fetching)</h3>
<p> Next.js 中的数据提取允许您根据应用程序的用例以不同的方式呈现内容。其中包括使用服务器端渲染或静态生成进行预渲染,以及使用增量静态再生成在运行时更新或创建内容。<br><br> 数据获取相关的几个函数:<br><br> (1) getStaticProps,用于构建时获取一些静态数据,默认情况下只会在构建时执行一次,之后的每次请求都会使用构建时的数据。<br> (2) getStaticPaths,从使用动态路由的页面导出名为 getStaticPaths 的函数时,Next.js 将静态预渲染 getStaticPaths 指定的所有路径。<br> (3) getServerSideProps,从页面导出名为 getServerSideProps(服务器端渲染)的函数时,Next.js 将使用getServerSideProps 返回的数据在每次请求时预渲染该页面。<br> (4) getInitialProps,是一个遗留的API。建议使用 getStaticProps 或 getServerSideProps。</p>
<p> </p>
<h3>3. getStaticProps</h3>
<p> 如果从页面导出一个名为 getStaticProps(静态站点生成)的函数,Next.js 将在构建时使用 getStaticProps 返回的 props 预渲染此页面。<br><br> getStaticProps 适用于如下情景:<br><br> (1) 渲染页面所需的数据在用户请求之前的构建时(build)可用<br> (2) 数据来自 Headless CMS<br> (3) 页面必须预渲染(用于 SEO)并且速度非常快 —— getStaticProps 生成 HTML 和 JSON 文件,这两个文件都可以通过 CDN 缓存以提高性能<br> (4) 数据可以被公开缓存(不是特定于用户的)。在某些特定情况下,可以通过使用中间件重写路径来绕过此条件。<br><br> getStaticProps 何时运行(被调用):<br><br> (1) 在 next build 期间,getStaticProps 会被调用<br> (2) 当使用 fallback:true 时,getStaticProps 在后台运行<br> (3) 当使用 fallback: blocking 时,初始渲染之前调用 getStaticProps<br> (4) 当使用 revalidate 时,getStaticProps 在后台运行<br> (5) 当使用 revalidate 时,getStaticProps 在后台按需运行<br><br> 注:getStaticProps 始终在服务器上运行,而从不在客户端上运行。可以使用此工具 (https://next-code-elimination.vercel.app/) 验证在 getStaticProps 中编写的代码是否已从客户端捆绑包中删除。<br> 使用 fallback:true,表示如果访问的页面不存在,Next.js 会返回一个 fallback 页面。这个 fallback 页面会在客户端重新生成,并在浏览器中显示。<br> 使用 fallback: blocking,表示页面的渲染将在所有数据准备就绪后再进行。 <br> 使用 revalidate,即增量静态再生成,getStaticProps 将在后台运行,同时重新验证陈旧页面,并将新页面提供给浏览器。<br><br> getStaticProps 的限制性:<br><br> (1)getStaticProps 无法访问传入请求(如查询参数或HTTP标头),因为它生成静态 HTML。如果需要访问页面请求,请考虑除了使用 getStaticProps 之外还使用中间件。<br> (2)getStaticProps 只能从页面导出。不能从非页面文件、_app、_document 或 _error 导出它。这种限制的原因之一是 React 需要在渲染页面之前拥有所有所需的数据。<br> (3)必须将导出 getStaticProps 作为一个独立函数使用 —— 如果将 getStaticProps 添加为页面组件的属性,它将不起作用。<br> <br> 在开发模式(next dev)中,将对每个请求调用 getStaticProps,可以暂时绕过静态生成。例如,可能正在使用无头 CMS,并且希望在草稿发布之前预览草稿。<br><br> <strong>1) 使用 getStaticProps 从 external API 获取数据</strong><br><br> 示例,创建 src/pages/render/data1.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ repo }) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> repo.stargazers_count
}
export const getStaticProps </span>= async () =><span style="color: rgba(0, 0, 0, 1)"> {
const res </span>= await fetch('https://api.github.com/repos/vercel/next.js'<span style="color: rgba(0, 0, 0, 1)">)
const repo </span>=<span style="color: rgba(0, 0, 0, 1)"> await res.json()
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: { repo } }
}</span></pre>
</div>
<p> 开发模式运行 nextjs-demo 项目,即运行 npm run dev 命令。<br> <br> 使用浏览器访问 http://localhost:3000/render/data1,显示内容如下:<br><br> Home Login # 菜单<br> 110193<br> Footer<br><br> <strong>2) 直接编写服务器端代码</strong><br><br> 由于 getStaticProps 仅在服务器端运行,因此它永远不会在客户端运行。它甚至不会包含在浏览器的 JS 捆绑包中,因此可以直接编写数据库查询,而无需将其发送到浏览器。<br><br> 这意味着,可以直接在 getStaticProps 中编写服务器端代码,而不是从 getStaticProps 获取API路由(它本身从外部源获取数据)。<br><br> 示例,API 路由用于从 CMS 获取一些数据。然后直接从 getStaticProps 调用该 API 路由。这会产生额外的调用,从而降低性能。相反,从 CMS 获取数据的逻辑可以通过使用 lib/ 目录来共享。然后可以与 getStaticProps 共享。<br> <br> 创建 src/pages/render/lib/load-posts.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> The following function is shared</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> with getStaticProps and API routes</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> from a `lib/` directory</span>
export const loadPosts = async () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Call an external API endpoint to get posts</span>
const res = await fetch('https://.../posts/'<span style="color: rgba(0, 0, 0, 1)">)
const data </span>=<span style="color: rgba(0, 0, 0, 1)"> await res.json()
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> data
}</span></pre>
</div>
<p> </p>
<p> 创建 src/pages/render/blog.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> pages/blog.js</span>
import { loadPosts } from './lib/load-posts'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This function runs only on the server side</span>
export const getStaticProps = async () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Instead of fetching your `/api` route you can call the same</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> function directly in `getStaticProps`</span>
const posts =<span style="color: rgba(0, 0, 0, 1)"> await loadPosts()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Props returned will be passed to the page component</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: { posts } }
}</span></pre>
</div>
<p><br> 如果不使用 API 路由来获取数据,则可以在 getStaticProps 中直接使用 fetch API 来获取数据。<br><br> <strong>3) 静态生成 HTML 和 JSON</strong><br><br> 当在构建时预渲染带有 getStaticProps 的页面时,除了页面 HTML 文件外,Next.js 还会生成一个 JSON 文件,其中包含运行 getStaticProps 的结果。<br><br> 此 JSON 文件将用于通过 next/link 或 next/router 的客户端路由。当导航到使用 getStaticProps 预渲染的页面时,Next.js 会获取这个 JSON 文件(在构建时预先计算),并将其用作页面组件的道具。这意味着客户端页面转换不会调用 getStaticProps,因为只使用导出的 JSON。<br><br> 当使用增量静态生成时,getStaticProps 将在后台执行,以生成客户端导航所需的 JSON。可能会以对同一页面发出多个请求的形式看到这一点,但是,这是有意的,对最终用户性能没有影响。</p>
<p> </p>
<h3>4. getStaticPaths</h3>
<p> 如果页面具有动态路由并使用 getStaticProps,则需要定义要静态生成的路径列表。<br><br> 从使用动态路由的页面导出名为 getStaticPaths(静态站点生成)的函数时,Next.js 使用 getStaticPaths 指定静态预渲染所有路径。<br><br> getStaticPaths 适用于如下情景:<br><br> (1) 数据来自 Headless CMS<br> (2) 数据来自数据库<br> (3) 数据来自文件系统<br> (4) 数据可以公开缓存(不是特定于用户)<br> (5) 页面必须预渲染(用于SEO)并且速度非常快 —— getStaticProps 生成 HTML 和 JSON 文件,这两个文件都可以通过 CDN 缓存以提高性能<br><br> 注:每种情景都是基于静态预渲染使用动态路由的页面<br><br> getStaticPaths 何时运行(被调用):<br><br> (1) 在 next build 期间,getStaticProps 需要 getStaticPaths 运行返回路径列表 <br> (2) 当使用 fallback:true 时,getStaticProps 在后台运行,需要 getStaticPaths 运行返回路径列表<br> (3) 当使用 fallback: blocking 时,在初始渲染之前调用 getStaticProps,需要 getStaticPaths 运行返回路径列表<br><br> 注:getStaticPaths 将只在生产中的构建(build)过程中运行,在运行时不会调用它。可以使用此工具 (https://next-code-elimination.vercel.app/) 验证在 getStaticPaths 中编写的代码是否已从客户端捆绑包中删除。<br><br> getStaticPaths 的限制性:<br><br> (1) getStaticPaths 必须与 getStaticProps 一起使用<br> (2) 不能将 getStaticPaths 与 getServerSideProps 一起使用<br> (3) 可以从同样使用 getStaticProps 的动态路由导出 getStaticPaths<br> (4) 无法从非页面文件(例如组件文件夹)导出 getStaticPaths<br> (5) 必须将 getStaticPaths 导出为独立函数,而不是页面组件的属性<br><br> 在开发模式(next dev)中,将对每个请求调用 getStaticPaths。<br><br> 示例1,getStaticPaths 允许控制在构建过程中生成哪些页面,而不是通过 fallback 按需生成。创建 src/pages/render/repo/.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ repo }) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> repo.stargazers_count
}
export const getStaticPaths </span>= async () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
paths: [
{
params: {
name: </span>'next.js'<span style="color: rgba(0, 0, 0, 1)">,
},
}, </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> See the "paths" section below</span>
<span style="color: rgba(0, 0, 0, 1)"> ],
fallback: </span><span style="color: rgba(0, 0, 255, 1)">true</span>, <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> false or "blocking"</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}
export const getStaticProps </span>= async () =><span style="color: rgba(0, 0, 0, 1)"> {
const res </span>= await fetch('https://api.github.com/repos/vercel/next.js'<span style="color: rgba(0, 0, 0, 1)">)
const repo </span>=<span style="color: rgba(0, 0, 0, 1)"> await res.json()
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: { repo } }
}</span></pre>
</div>
<p> <br> 示例2,可以通过返回一个空的路径数组来推迟按需生成所有页面。创建 src/pages/render/posts/.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ posts }) =><span style="color: rgba(0, 0, 0, 1)"> {
...
}
export async </span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> getStaticPaths() {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> When this is true (in preview environments) don't</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> prerender any static pages</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> (faster builds, but slower initial page load)</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (process.env.SKIP_BUILD_STATIC_GENERATION) {
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> {
paths: [],
fallback: </span>'blocking'<span style="color: rgba(0, 0, 0, 1)">,
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Call an external API endpoint to get posts</span>
const res = await fetch('https://.../posts'<span style="color: rgba(0, 0, 0, 1)">)
const posts </span>=<span style="color: rgba(0, 0, 0, 1)"> await res.json()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Get the paths we want to prerender based on posts</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> In production environments, prerender all pages</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> (slower builds, but faster initial page load)</span>
const paths = posts.map((post) =><span style="color: rgba(0, 0, 0, 1)"> ({
params: { id: post.id },
}))
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> { fallback: false } means other routes should 404</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> { paths, fallback: <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)"> }
}
export const getStaticProps </span>= async () =><span style="color: rgba(0, 0, 0, 1)"> {
...
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: { posts } }
}</span></pre>
</div>
<p> </p>
<h3>5. getServerSideProps</h3>
<p> 如果从页面导出名为 getServerSideProps(服务器端渲染)的函数,Next.js 将使用 getServerSideProps 返回的数据在每次请求时预渲染该页面。<br><br> 只有在需要渲染数据必须在请求时提取的页面时,才应使用 getServerSideProps。如果在请求期间不需要渲染数据,则应考虑在客户端或 getStaticProps 上获取数据。<br> <br> getServerSideProps 适用于如下情景:<br><br> 与 SEO 不相关,也不需要预渲染页面。数据经常更新,需要在请求时获取数据。<br><br> getServerSideProps 何时运行(被调用):<br><br> (1) 当直接请求使用 getServerSideProps 的页面时,getServerSideProps 会在请求时运行,并且此页面将使用返回的 props 进行预渲染<br> (2) 当通过 next/link 或 next/router 在客户端页面转换时请求使用 getServerSideProps 页面时,next.js 会向服务器发送一个 API 请求,该请求运行 getServerSideProps<br><br> 注:页面请求都会调用 getServerSideProps。getServerSideProps 只在服务器端运行,从不在浏览器上运行。<br><br> getServerSideProps 的限制性:<br><br> (1) 只有在配置了缓存控制标头的情况下才会缓存<br> (2) 无法从非页面文件(例如组件文件夹)导出 getServerSideProps<br> (3) 必须将 getServerSideProps 导出为独立函数,而不是页面组件的属性<br><br> 示例1,在请求时使用 getServerSideProps 获取数据,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ data }) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Render data ...</span>
<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This gets called on every request</span>
export const getServerSideProps = async () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Fetch data from external API</span>
const res = await fetch(`https:<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">.../data`)</span>
const data =<span style="color: rgba(0, 0, 0, 1)"> await res.json()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Pass data to the page via props</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: { data } }
}</span></pre>
</div>
<p><br> 注:如果 getServerSideProps 函数内 fetch 的 API 路由 (`https://.../data`) 是本地资源,就是不必要且效率低下的方法,因为它将导致由于服务器上运行 getServerSideProps 和 API 路由而产生额外的请求。<br><br> 本地数据库、JSON 文件等资源,最好在 getServerSideProps 函数内直接调用,可以适当降低服务器的负载压力。当然,具体情况具体分析,在安全、效率和开发速度等因素之间,需要开发者自己根据产品需要做好平衡。<br> <br> 示例2,使用服务器端渲染(SSR)进行缓存,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> ({ data }) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Render data ...</span>
<span style="color: rgba(0, 0, 0, 1)">
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> This value is considered fresh for ten seconds (s-maxage=10).</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If a request is repeated within the next 10 seconds, the previously</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> cached value will still be fresh. If the request is repeated before 59 seconds,</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> the cached value will be stale but still render (stale-while-revalidate=59).</span>
<span style="color: rgba(0, 128, 0, 1)">//
</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> In the background, a revalidation request will be made to populate the cache</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> with a fresh value. If you refresh the page, you will see the new value.</span>
export const getServerSideProps = async ({ req, res }) =><span style="color: rgba(0, 0, 0, 1)"> {
res.setHeader(
</span>'Cache-Control'<span style="color: rgba(0, 0, 0, 1)">,
</span>'public, s-maxage=10, stale-while-revalidate=59'<span style="color: rgba(0, 0, 0, 1)">
)
...
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Pass data to the page via props</span>
<span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> { props: { data } }
}</span></pre>
</div>
<p><br> 可以通过修改配置,在每页的基础上显式设置运行时,例如:</p>
<div class="cnblogs_code">
<pre> export const config =<span style="color: rgba(0, 0, 0, 1)"> {
runtime: </span>'nodejs', <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> or "edge"</span>
<span style="color: rgba(0, 0, 0, 1)"> }
export const getServerSideProps </span>= async () => {}</pre>
</div>
<p><br> 注:getServerSideProps 可以与 Serverless 和 Edge Runtime 一起使用,并且可以在两者中设置 props。但是,当前在 Edge Runtime 中,无权访问响应对象。这意味着不能在 getServerSideProps 中添加 cookie。要访问响应对象,应该继续使用 Node.js Runtime,这是默认的 Runtime。<br><br> 如果在 getServerSideProps 中抛出错误,它将显示 pages/500.js 文件。在开发过程中,不会使用此文件,而是显示 dev 覆盖。</p>
<p> </p>
<h3>6. Web 表单</h3>
<p> Web 表单具有客户端-服务器关系,一般被用于发送由 Web 服务器处理的数据以进行处理和存储。表单本身就是客户端,服务器是任何可以在需要时用于存储、检索和发送数据的存储机制。<br><br> 示例,Web 表单提交数据到 /api/login,创建 src/pages/api/login.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> (req, res) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Get data submitted in request's body.</span>
const body =<span style="color: rgba(0, 0, 0, 1)"> req.body
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Optional logging to see the responses</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> in the command line where next.js app is running.</span>
console.log('body: '<span style="color: rgba(0, 0, 0, 1)">, body)
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (!body.username || !<span style="color: rgba(0, 0, 0, 1)">body.password) {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Sends a HTTP bad request error code</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> res.status(400).json({ data: 'Username or password not found'<span style="color: rgba(0, 0, 0, 1)"> })
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Found the name.</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Sends a HTTP success code</span>
res.status(200<span style="color: rgba(0, 0, 0, 1)">).json({ data: `${body.username} ${body.password}` })
}</span></pre>
</div>
<p><br> 创建 src/pages/login.js 文件,代码如下:</p>
<div class="cnblogs_code">
<pre> export <span style="color: rgba(0, 0, 255, 1)">default</span> () =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Handles the submit event on form submit.</span>
const handleSubmit = async (event) =><span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Stop the form from submitting and refreshing the page.</span>
<span style="color: rgba(0, 0, 0, 1)"> event.preventDefault()
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Get data from the form.</span>
const data =<span style="color: rgba(0, 0, 0, 1)"> {
username: event.target.username.value,
password: event.target.password.value,
}
console.log(</span>'data: '<span style="color: rgba(0, 0, 0, 1)">, data)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Send the data to the server in JSON format.</span>
const JSONdata =<span style="color: rgba(0, 0, 0, 1)"> JSON.stringify(data)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> API endpoint where we send form data.</span>
const endpoint = '/api/login'
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Form the request for sending data to the server.</span>
const options =<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> The method is POST because we are sending data.</span>
method: 'POST'<span style="color: rgba(0, 0, 0, 1)">,
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Tell the server we're sending JSON.</span>
<span style="color: rgba(0, 0, 0, 1)"> headers: {
</span>'Content-Type': 'application/json'<span style="color: rgba(0, 0, 0, 1)">,
},
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Body of the request is the JSON data we created above.</span>
<span style="color: rgba(0, 0, 0, 1)"> body: JSONdata,
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Send the form data to our forms API on Vercel and get a response.</span>
const response =<span style="color: rgba(0, 0, 0, 1)"> await fetch(endpoint, options)
console.log(</span>'response: '<span style="color: rgba(0, 0, 0, 1)">, response)
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Get the response data from server as JSON.</span>
<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> If server returns the name submitted, that means the form works.</span>
const result =<span style="color: rgba(0, 0, 0, 1)"> await response.json()
alert(`Login info: ${result.data}`)
}
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
</span><form onSubmit={handleSubmit}>
<p>&nbsp;</p>
<p><label <span style="color: rgba(0, 0, 255, 1)">for</span>="username">Username: </label>
<input type="text" id="username" name="username" required value="admin"/></p>
<p><label <span style="color: rgba(0, 0, 255, 1)">for</span>="password">Password: </label>
<input type="text" id="password" name="password" required value="123456"/></p>
<p><button type="submit">Submit</button></p>
</form>
<span style="color: rgba(0, 0, 0, 1)"> )
}</span></pre>
</div>
<p><br> 开发模式运行 nextjs-demo 项目,使用浏览器访问 http://localhost:3000/login,显示 Login 页面,点击 Submit 按钮,跳出提示对话框。<br><br><br></p><br><br>
来源:https://www.cnblogs.com/tkuang/p/17629464.html
頁:
[1]