</>
);
}
### 设置数据库
使用@vercel/postgres设置PostgreSQL数据库
1. 将项目推送到 GitHub
2. 设置 [Vercel]( ) 帐户并链接 GitHub 存储库以进行即时预览和部署
3. 创建项目并将其链接到Postgres 数据库
>
> 在上传的项目中,选择Storage
> Connect Store → Create New → Postgres → Continue
> 通过将数据库放置在同一区域或靠近应用程序代码,可以减少数据请求的延迟。
> 数据库区域一旦启动就无法更改。如果要使用其他区域,则应在创建数据库之前进行设置
> 导航到 .env.local 选项卡,单击“显示密钥”和“复制代码段”
> 导航到代码编辑器并将 .env.example 文件重命名为 .env 。粘贴从 Vercel 复制的内容。
> 重要提示:转到您的 .gitignore 文件并确保 .env 位于忽略的文件中,以防止在推送到 GitHub 时暴露您的数据库机密。
> 最后,在终端中运行 npm i @vercel/postgres 以安装 Vercel Postgres SDK。
>
>
>
4. 使用初始数据为数据库设定种子
文件中 package.json ,将以下行添加到脚本中
“scripts”: {
“build”: “next build”,
“dev”: “next dev”,
“start”: “next start”,
// this
“seed”: “node -r dotenv/config ./scripts/seed.js”
},
在终端运行:npm run seed
将placeholder-data.js数据导入数据库中(初始数据)
在Vercel查看数据是否填充完毕,选择Storage,下滑找到侧边栏Data,可查询表格,以及sql查询
### 获取数据
1. 了解一些获取数据的方法:API、ORM、SQL 等。
2. 服务器组件如何帮助您更安全地访问后端资源。
3. 什么是网络瀑布。
4. 如何使用 JavaScript 模式实现并行数据获取。
API 是应用程序代码和数据库之间的中间层
* 第三方提供的api
* 如果要从客户端提取数据,则希望在服务器上运行一个 API 层,以避免向客户端公开数据库机密。(在客户端上获取数据时不应直接查询数据库,因为这会暴露数据库机密。)
* 使用[route-handlers]( ) 创建 API 终端节点
>
> 注:如果你使用的是 React Server 组件(在服务器上获取数据),你可以跳过 API 层,直接查询你的数据库,而不会冒着将数据库机密暴露给客户端的风险。
>
>
>
**React Server 组件优点**
1. 服务器组件支持 Promise,为数据获取等异步任务提供更简单的解决方案。 您可以使用 async/await 语法,而无需使用 useEffect、useState 或数据获取库。
2. 服务器组件在服务器上执行,因此您可以将昂贵的数据提取和逻辑保留在服务器上,并且只将结果发送到客户端。
3. 如前所述,由于服务器组件在服务器上执行,因此您可以直接查询数据库,而无需额外的 API 层。
在/app/lib/data.ts文件中,导入 sql 函数,此函数允许您查询数据库
// 查询数据库
import { sql } from ‘@vercel/postgres’;
在/app/dashboard/page.tsx文件中
/**
* Page 是一个异步组件。这允许您用于 await 获取数据。
* 还有 3 个接收数据的组件: 、 和 。
* 它们目前被注释掉,以防止应用程序出错。
*
*/
import { Card } from ‘@/app/ui/dashboard/cards’;
import RevenueChart from ‘@/app/ui/dashboard/revenue-chart’;
import LatestInvoices from ‘@/app/ui/dashboard/latest-invoices’;
import { lusitana } from ‘@/app/ui/fonts’;
export default async function Page() {
return (
<h1 className={${lusitana.className} mb-4 text-xl md:text-2xl}>
Dashboard
{/* */}
{/* */}
{/* */}
{/* */}
{/* */}
{/* */}
);
}
在/app/lib/data.ts文件中引入fetchRevenue 函数
然后,取消对组件的 注释
导航到组件文件 ( /app/ui/dashboard/revenue-chart.tsx ) 并取消注释其中的代码。
检查你的 localhost,你应该能够看到一个使用 revenue 数据的图表。
…
后续相关自行查看官方文档
import { Card } from ‘@/app/ui/dashboard/cards’;
import RevenueChart from ‘@/app/ui/dashboard/revenue-chart’;
import LatestInvoices from ‘@/app/ui/dashboard/latest-invoices’;
import { lusitana } from ‘@/app/ui/fonts’;
// 获取数据
import { fetchRevenue } from ‘@/app/lib/data’;
export default async function Page() {
// 获取数据库数据
const revenue = await fetchRevenue();
// …
}
注意:
1. 数据请求无意中相互阻塞,从而创建了一个请求瀑布(同步)。
>
> “瀑布流”是指一系列网络请求,这些请求依赖于先前请求的完成情况。在数据获取的情况下,每个请求只能在前一个请求返回数据后开始。(影响性能)
> `--------------------------------------------------------------------------`
> 避免瀑布的常用方法是同时并行启动所有数据请求。(异步)
> 在 JavaScript 中,您可以使用 Promise.all() or Promise.allSettled() 函数同时启动所有 promise。例如,在 中 data.ts ,我们在 fetchCardData() 函数中使用 Promise.all()
> 开始同时执行所有数据提取,这可能会导致性能提升
> 但是,仅依赖这种 JavaScript 模式有一个缺点:如果一个数据请求比其他所有数据请求慢,会发生什么情况?
>
>
>
2. 默认情况下,Next.js预渲染路由以提高性能,这被称为静态渲染。因此,如果您的数据发生更改,在视图上并不会响应更改
### 静态和动态渲染
目前dashboard是静态的,并没有实时数据更新
**静态渲染**
1. 更快的网站 - 预渲染的内容可以缓存并在全球范围内分发。这确保了世界各地的用户可以更快、更可靠地访问您网站的内容。
2. 减少服务器负载 - 由于内容已缓存,因此服务器不必为每个用户请求动态生成内容。
3. SEO - 搜索引擎爬虫更容易对预渲染的内容进行索引,因为内容在页面加载时已经可用。这可以提高搜索引擎排名。
**动态渲染**
1. 实时数据 - 动态呈现允许应用程序显示实时或经常更新的数据。这非常适合数据经常更改的应用程序。
2. 特定于用户的内容 - 更轻松地提供个性化内容(如仪表板或用户配置文件),并根据用户交互更新数据。
3. 请求时间信息 - 动态呈现允许您访问只能在请求时知道的信息,例如 Cookie 或 URL 搜索参数。
在/app/lib/data.ts 文件中使用unstable\_noStore,并将注释解开(测速,人工阻塞)
[Segment Config Option]( )
/**
* 使用在服务器组件或数据获取函数中调用 unstable_noStore 的 Next.js API 来选择退出静态渲染
* 注意: unstable_noStore 是一个实验性 API,将来可能会更改。
* 如果您希望在自己的项目中使用稳定的 API,也可以使用 Segment Config Option export const dynamic = “force-dynamic” 。
*/
import { unstable_noStore as noStore } from ‘next/cache’;
export async function fetchRevenue() {
// 此处添加 noStore() 以防止响应被缓存.
noStore();
try {
// Artificially delay a response for demo purposes.
// Don’t do this in production 😃
// 解开注释
console.log(‘Fetching revenue data…’);
await new Promise((resolve) => setTimeout(resolve, 3000));
const data = await sql<Revenue>`SELECT \* FROM revenue`;
// 解开注释
console.log('Data fetch completed after 3 seconds.');
return data.rows;
} catch (error) {
console.error(‘Database Error:’, error);
throw new Error(‘Failed to fetch revenue data.’);
}
}
// …
### Streaming( 流)
流式处理是一种数据传输技术,它允许您将路由分解为更小的“块”,并在它们准备就绪时逐步将它们从服务器流式传输到客户端。
通过流式传输,您可以防止缓慢的数据请求阻止您的整个页面。这允许用户查看页面的某些部分并与之交互,而无需等待加载所有数据后才能向用户显示任何 UI。
**实现流式处理**
1. 在页面级别,使用 loading.tsx 文件。
2. 对于特定组件,使用 `<Suspense>` .
1、在文件夹中 /app/dashboard ,创建一个名为 loading.tsx (特殊文件:页面加载时显示)
// 导入骨架屏ui
import DashboardSkeleton from ‘@/app/ui/skeletons’;
export default function Loading() {
return ;
}
2、由于 `<SideNav>` 是静态的,因此会立即显示。用户可以在加载动态内容时与之 交互。
3、用户不必等待页面完成加载后再导航(这称为可中断导航)。
注意:loading.tsx 级别高于 /invoices/page.tsx 和 /customers/page.tsx页面,所以loading.tsx 也应用在了/invoices/page.tsx 和 /customers/page.tsx页面中。
解决:
使用路由组来更改此设置,在/app/dashboard下创建文件夹/(overview) ,将/app/dashboard/page.tsx和/app/dashboard/loading.tsx移入新文件中。
>
> 路由组允许您将文件组织到逻辑组中,而不会影响 URL 路径结构。使用括号创建新文件夹时 () ,该名称不会包含在 URL 路径中。所以 /dashboard/(overview)/page.tsx 变成了 /dashboard .
> `-----------------`
> 在这里,你使用路由组来确保 loading.tsx 仅适用于仪表板概述页面。但是,您也可以使用路由组将应用程序分成多个部分(例如路由 (marketing) 和 (shop) 路由),或者对于较大的应用程序按团队分隔。
>
>
>
4、Suspense 允许您延迟渲染应用程序的某些部分,直到满足某些条件(例如加载数据)。您可以将动态组件包装在 Suspense 中。然后,向它传递一个回退组件,以便在动态组件加载时显示。
如果您还记得缓慢的数据请求, fetchRevenue() 则这是减慢整个页面速度的请求。您可以使用 Suspense 仅流式传输此组件并立即显示页面 UI 的其余部分,而不是阻止您的页面。
在 /dashboard/(overview)/page.tsx 删除所有 fetchRevenue() 实例及其数据,并导入Suspense、RevenueChartSkeleton
// 移除fetchRevenue
import { fetchLatestInvoices, fetchCardData } from ‘@/app/lib/data’;
// 导入Suspense ,RevenueChartSkeleton
import { Suspense } from ‘react’;
import { RevenueChartSkeleton } from ‘@/app/ui/skeletons’;
// const revenue = await fetchRevenue(); 删除
// 修改
<Suspense fallback={}>
修改/app/ui/dashboard/revenue-chart.tsx文件
import { fetchRevenue } from ‘@/app/lib/data’;
export default async function RevenueChart() { // 移除参数
const revenue = await fetchRevenue(); // 获取数据
// …
return (
// …
);
}
练习
将card组件包裹在`Suspense`中,一个一个加载时会出现popping effect,可能会造成视觉上的不和谐。
解决:
1、在/app/dashboard/page.tsx删除card相关的实例及其数据,导入CardsSkeleton 、CardWrapper
// 修改(添加CardsSkeleton )
import { RevenueChartSkeleton,LatestInvoicesSkeleton,CardsSkeleton } from ‘@/app/ui/skeletons’;
// 导入CardWrapper 组件
import CardWrapper from ‘@/app/ui/dashboard/cards’;
// 删掉
// const {totalPaidInvoices,totalPendingInvoices,numberOfInvoices,numberOfCustomers} = await fetchCardData()
{/*
*/}
// 修改成
<Suspense fallback={}>
2、在/app/ui/dashboard/cards.tsx文件中获取数据,将card注释去掉
// …
import { fetchCardData } from ‘@/app/lib/data’;
// …
export default async function CardWrapper() {
const {
numberOfInvoices,
numberOfCustomers,
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData();
return (
<>
</>
);
}
3、刷新页面,您应该会看到所有卡同时加载。当您希望同时加载多个组件时,可以使用此模式。
### 搜索与分页
您的搜索功能将跨越客户端和服务器。当用户在**客户端搜索**发票时,URL 参数将被更新,数据将在**服务器**上获取,并且表格将使用新数据在服务器上重新呈现。
| 客户端钩子 | 说明 |
| --- | --- |
| useSearchParams | 允许您访问当前 URL 的参数 |
| usePathname | 允许您读取当前 URL 的路径名 |
| useRouter | 以编程方式启用客户端组件内的路由之间的导航 |
“use client” - 这是一个客户端组件,这意味着您可以使用事件侦听器和钩子。
在/app/ui/search.tsx文件中创建一个新 handleSearch 函数,并向 元素添加一个 onChange 侦听器。
‘use client’;
import { MagnifyingGlassIcon } from ‘@heroicons/react/24/outline’;
export default function Search({ placeholder }: { placeholder: string }) {
// this
function handleSearch(term: string) {
console.log(term);
}
return (
// …
<input
className=“peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500”
placeholder={placeholder}
onChange={(e) => {
handleSearch(e.target.value);
}}
/>
// …
);
}
**使用搜索参数更新 URL**
1、在内部 handleSearch, 使用新 searchParams 变量创建一个新 URLSearchParams 实例。
>
> URLSearchParams 是一个 Web API,它提供用于操作 URL 查询参数的实用工具方法。
> 您可以使用它来获取 params 字符串,而不是创建复杂的字符串文字 ?page=1&query=a 。
>
>
>
2、set 基于用户输入的参数字符串。如果输入为空,则需要 delete
3、使用 Next.js useRouter 和 usePathname hooks 来更新 URL。
>
> ${pathname} 是当前路径,即 “/dashboard/invoices” 。
> params.toString() 将此输入转换为 URL 友好的格式,即query=xxx
>
>
>
4、由于 Next.js 的客户端导航,因此无需重新加载页面即可更新 URL。
5、保持url和输入同步
<input
className=“peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500”
placeholder={placeholder}
onChange={(e) => {
handleSearch(e.target.value);
}}
defaultValue={searchParams.get(‘query’)?.toString()}
/>
// 如果使用 state 来管理输入的值,则可以使用该 value 属性使其成为受控组件。这意味着 React 将管理输入的状态。
// 由于您没有使用 state,因此可以使用 defaultValue .这意味着本机输入将管理自己的状态。这没关系,因为您将搜索查询保存到 URL 而不是状态。
6、搜索防抖(减少发送到数据库的请求数,从而节省资源)
npm i use-debounce
/app/ui/search.tsx
‘use client’;
import { MagnifyingGlassIcon } from ‘@heroicons/react/24/outline’;
import { useSearchParams, usePathname, useRouter } from ‘next/navigation’;
// 防抖
import { useDebouncedCallback } from ‘use-debounce’;
export default function Search() {
const searchParams = useSearchParams();
const pathname = usePathname();
const { replace } = useRouter();
const handleSearch = useDebouncedCallback((term) => {
const params = new URLSearchParams(searchParams);
// 设置页数为1(分页器)
params.set(‘page’, ‘1’);
if (term) {
params.set(‘query’, term);
} else {
params.delete(‘query’);
}
// 使用用户的搜索数据更新 URL
replace(${pathname}?${params.toString()});
}, 300);
}
/app/dashboard/invoices/page.tsx
// 页面组件接受一个名为 searchParams 的 prop,因此您可以将当前的 URL 参数传递给组件
export default async function Page({
searchParams,
}: {
searchParams?: {
query?: string;
page?: string;
};
}) {
const query = searchParams?.query || ‘’;
const currentPage = Number(searchParams?.page) || 1;
const totalPages = await fetchInvoicesPages(query);
return (
// …
<Suspense key={query + currentPage} fallback={}>
// …
);
}
查看/app/ui/invoices/table.tsx,无需修改
**添加分页**
/app/dashboard/invoices/page.tsx
// …
import { fetchInvoicesPages } from ‘@/app/lib/data’;
export default async function Page({
searchParams,
}: {
searchParams?: {
query?: string,
page?: string,
},
}) {
const query = searchParams?.query || ‘’;
const currentPage = Number(searchParams?.page) || 1;
const totalPages = await fetchInvoicesPages(query);
return (
// …
// …
);
}
在/app/ui/invoices/pagination.tsx文件中取消注释,导入usePathname, useSearchParams并使用,创建一个名为 createPageURL。与搜索类似,您将用于 URLSearchParams 设置新的页码,并 pathName 创建 URL 字符串。
import { usePathname, useSearchParams } from ‘next/navigation’;
export default function Pagination({ totalPages }: { totalPages: number }) {
const pathname = usePathname();
const searchParams = useSearchParams();
const currentPage = Number(searchParams.get(‘page’)) || 1;
const createPageURL = (pageNumber: number | string) => {
const params = new URLSearchParams(searchParams);
params.set(‘page’, pageNumber.toString());
return ${pathname}?${params.toString()};
};
// …
}
最后,当用户键入新的搜索查询时,您需要将页码重置为 1。
/app/ui/search.tsx
const handleSearch = useDebouncedCallback((term) => {
const params = new URLSearchParams(searchParams);
// 设置页数为1
params.set(‘page’, ‘1’);
if (term) {
params.set(‘query’, term);
} else {
params.delete(‘query’);
}
replace(${pathname}?${params.toString()});
}, 300);
### 服务器操作数据
[zod]( )
### 错误处理
如何使用特殊 error.tsx 文件捕获路由段中的错误,并向用户显示回退 UI。
如何使用 notFound 函数和 not-found 文件处理 404 错误(对于不存在的资源)。
### 提高可访问性
表单验证
Next.js 配合使用 eslint-plugin-jsx-a11y 以实现辅助功能最佳实践。
使用服务器操作实现服务器端验证
使用 useFormState 钩子显示表单错误
在部署应用程序之前在本地运行 lint 以捕获可访问性问题
在 package.json 文件中添加 next lint 为脚本
“scripts”: {
“build”: “next build”,
“dev”: “next dev”,
“seed”: “node -r dotenv/config ./scripts/seed.js”,
“start”: “next start”,
“lint”: “next lint”
},
npm run lint
### 身份验证
使用 NextAuth.js 向应用添加身份验证
使用中间件重定向用户并保护您的路由
使用 React useFormStatus 并 useFormState 处理待处理的状态和表单错误
### 最后
由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**


useFormState 钩子显示表单错误
在部署应用程序之前在本地运行 lint 以捕获可访问性问题
在 package.json 文件中添加 next lint 为脚本
“scripts”: {
“build”: “next build”,
“dev”: “next dev”,
“seed”: “node -r dotenv/config ./scripts/seed.js”,
“start”: “next start”,
“lint”: “next lint”
},
npm run lint
### 身份验证
使用 NextAuth.js 向应用添加身份验证
使用中间件重定向用户并保护您的路由
使用 React useFormStatus 并 useFormState 处理待处理的状态和表单错误
### 最后
由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!
**[开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】](https://bbs.csdn.net/topics/618166371)**
[外链图片转存中...(img-70CNhXWl-1714314754551)]
[外链图片转存中...(img-9VArrAfY-1714314754552)]
本文介绍了一个Next.js应用中关于数据获取、页面加载优化和用户体验的实践。内容涉及动态导入、 Suspense、防抖动以及利用Next.js API进行数据管理,包括fetchRevenue函数的实现和优化,以及在组件如RevenueChart、CardWrapper和Search中的应用。同时,文章还讨论了如何通过使用useSearchParams和Pagination组件来实现分页功能。
606

被折叠的 条评论
为什么被折叠?



