nextjs(持续学习中)

return (

<p className={${lusitana.className} text-xl text-gray-800 md:text-3xl md:leading-normal}>
Welcome to Acme. This is the example for the{' '}
Next.js Learn Course

, brought to you by Vercel.

);
}

复制代码
在顶级 /public 文件夹下提供静态资产  
 **默认 /public 文件夹会被映射网站的根目录**



> 
> 即/public/img.png -> /img.png
> 
> 
> 


使用常规 HTML(不推荐)

/**

* 使用常规 HTML,必须手动

* 确保您的图像在不同的屏幕尺寸上具有响应性

* 指定不同设备的图像大小

* 防止在加载图像时出现布局偏移

* 延迟加载用户视口之外的图像

* --------------------------------------------------

* 图像优化是 Web 开发中的一个大主题,本身可以被视为一个专业化。

* 您可以使用该 next/image 组件自动优化图像,而不是手动实现这些优化。

* ------------------------------

* 自动图像优化功能:

* 1、防止在加载图像时自动移动布局

* 2、调整图像大小以避免将大图像传送到视口较小的设备。

* 3、默认情况下延迟加载图像(图像在进入视口时加载->懒加载)。

* 4、在浏览器支持的情况下,以现代格式(如 WebP 和 AVIF)提供图像。

*

**/

复制代码
使用 next/image 组件自动优化图像(**推荐**)


在 /app/page.tsx 文件中,导入 next/image 组件。



> 
> 根据image 组件设置的宽高进行设定  
>  默认移动端优先原则,md: 更宽的屏幕 block:以块级元素显示  
>  className="hidden md:block" 默认hidden 隐藏,md: 更宽的屏幕显示
> 
> 
> 

/**

* 应该设置图片的宽高(避免布局偏移(从无到有))

* 宽高应该与原图的纵横比相同

* 您还会注意到该类 hidden 用于从移动屏幕上的 DOM 中删除图像

* md:block 桌面屏幕上显示图像。

**/

import Image from 'next/image';

export default function Page() {

return (

{/* Add Hero Images Here */}

{/* 显示在移动屏幕上,并在桌面上隐藏 */}

);
}

复制代码
### 创建布局和页面




| 特殊文件 | 说明 |
| --- | --- |
| page.tsx | 用于导出 React 组件,并且路由是可访问所必需的(不同文件下访问入口) |
| layout.tsx | 布局文件(不同文件下的布局入口) |
| loading.tsx | 创建后备 UI 以在页面内容**加载时显示**为替换 |


创建首页入口文件:/app/dashboard/page.tsx  
 url地址:http://localhost:3000/dashboard

export default function Page() {

return

Dashboard Page
;
}

复制代码
在Next.js中使用布局的一个好处是,在导航时,只有页面组件会更新,而布局不会重新呈现,这称为部分渲染。  
 创建首页布局文件:/app/dashboard/layout.tsx

import SideNav from '@/app/ui/dashboard/sidenav';

export default function Layout({ children }: { children: React.ReactNode }) {

return (

{children}

);
}

复制代码
### 页面导航

页面之间链接,通常使用 HTML 元素,每次切换页面都会被刷新。

在 Next.js 中,可以使用 组件在应用程序中的页面之间进行链接。
允许您使用 JavaScript 进行客户端导航。

复制代码
**next/link 如何实现一个Web 应用?**


* Next.js会自动按路由段拆分应用程序。
* 这与传统的 React SPA 不同,在传统的 React SPA 中,浏览器在初始加载时加载所有应用程序代码
* 按路由拆分代码意味着页面变得独立。如果某个页面抛出错误,应用程序的其余部分仍将正常工作。
* 此外,在生产环境中,每当组件出现在浏览器的视口中时**Link**,Next.js都会在后台**自动预取链接路由的代码**。当用户单击链接时,目标页面的代码已经在后台加载,这就是使页面转换近乎即时的原因!


打开/app/ui/dashboard/nav-links.tsx文件, 导入 Link 组件。

// 使用Link

import Link from 'next/link';

// 然后,找到 标签替换为

{link.name}

复制代码
获取用户的当前路径,Next.js提供了一个钩子函数usePathname()  
 使用客户端渲染(必须):作用于浏览器  
 默认情况下,Next.js应用程序使用 React Server 组件(服务端渲染)

'use client'; // 使用客户端渲染(必须)

import {

UserGroupIcon,

HomeIcon,

DocumentDuplicateIcon,

} from '@heroicons/react/24/outline';

// 导入钩子函数usePathname

import { usePathname } from 'next/navigation';

// usePathname搭配clsx使用

import clsx from 'clsx';

// 导航列表

const links = [

{ name: 'Home', href: '/dashboard', icon: HomeIcon },

{

name: 'Invoices',

href: '/dashboard/invoices',

icon: DocumentDuplicateIcon,

},

{ name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon },

];

export default function NavLinks() {

const pathname = usePathname();

return (

<>

{links.map((link) => {

const LinkIcon = link.icon;

return (

<Link

key={link.name}

href={link.href}

className={clsx(

'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',

{

'bg-sky-100 text-blue-600': pathname === link.href,

},

)}

>

{link.name}

);
})}
</>
);
}

复制代码
### 设置数据库


使用@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,无需修改



### 最后

我可以将最近整理的前端面试题分享出来,其中包含**HTML、CSS、JavaScript、服务端与网络、Vue、浏览器、数据结构与算法**等等,还在持续整理更新中,希望大家都能找到心仪的工作。


**篇幅有限,仅展示部分截图:**

![](https://img-blog.csdnimg.cn/img_convert/ff6b8e636a356a01389cd2de0211d347.png)

![](https://img-blog.csdnimg.cn/img_convert/b24f32dd81ead796fa80c27c763d262c.png)

![](https://img-blog.csdnimg.cn/img_convert/c6265dc2681708533916a8d7910506b3.png)
相关推荐
阿阳微客1 小时前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏
Chef_Chen6 小时前
从0开始学习R语言--Day18--分类变量关联性检验
学习
键盘敲没电7 小时前
【IOS】GCD学习
学习·ios·objective-c·xcode
海的诗篇_7 小时前
前端开发面试题总结-JavaScript篇(一)
开发语言·前端·javascript·学习·面试
AgilityBaby7 小时前
UE5 2D角色PaperZD插件动画状态机学习笔记
笔记·学习·ue5
AgilityBaby7 小时前
UE5 创建2D角色帧动画学习笔记
笔记·学习·ue5
武昌库里写JAVA9 小时前
iview Switch Tabs TabPane 使用提示Maximum call stack size exceeded堆栈溢出
java·开发语言·spring boot·学习·课程设计
一弓虽10 小时前
git 学习
git·学习
Moonnnn.12 小时前
【单片机期末】串行口循环缓冲区发送
笔记·单片机·嵌入式硬件·学习
viperrrrrrrrrr712 小时前
大数据学习(131)-Hive数据分析函数总结
大数据·hive·学习