1.文字优化:
(1)具体代码:
ts
///app/ui/fonts.ts
import { Inter } from 'next/font/google';
export const inter = Inter({
subsets: ['latin']
}
);
///app/layout.tsx
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';
export default function RootLayout({ children,}: { children: React.ReactNode;}) {
return (
<html lang="en">
<body className={`${inter.className} antialiased`}>{children}</body> </html>
);
}

(2)相关疑问:
问:为什么import { Inter } from 'next/font/google'中Inter要加大括号
答:这里涉及了命名导入,在命名导入时是要精确的导入内容的所以必须名字一模一样,并且必须加大括号 import { X } from 'xxx'
额外补充:默认导出导入关系和命名导出导入关系
1.默认导出导入关系:默认导出那这个文件当中只能导出一个 多了就会报错、就乱了、就无法精准接收
为什么只能一个?
因为:默认导出 = 这个文件的 "主角"一个文件只能有 一个主角。 如果是 export default const A = ... export default const B = ... export default const C = ... 这里就会覆盖,也就是最终模块的
default只会保留最后一个值(c),前面的都会被覆盖,相当于 "白写了"------ 这就是为什么说 "只能有一个"(多了会乱,失去意义)所以默认导入 = 跟名字无关,导入名字随便写。export default 导出你给它起什么名字,它就叫什么名字。
那如果一个文件里要导出很多东西怎么办?
用 命名导出(大括号那种) export const A = ... export const B = ... export const C = ... 别人导入时: import { A, B, C } from 'xxx'
2.命名导出导入关系:
命名导出就是为「一个文件导出多个内容」设计的,想导出多少就导出多少,完全不受限。
对方是 export const XXX你导入时 必须名字一模一样,错一个字母都不行 必须大括号 import { X } from 'xxx'
问:默认和命名导出本质上这两个是不能出现在一个文件里的吧?
答:在 JavaScript 的模块系统里,一个文件可以同时拥有一个默认导出(Default Export)和多个命名导出(Named Export)。
- 默认导出 (
export default) :像是一个国家的"国王",一个文件只能有一个。导入时不需要大括号:import CardWrapper from '...'。 - 命名导出 (
export function Card) :像是一个国家的"大臣",一个文件可以有无数个。导入时必须带大括号:import { Card } from '...'。
例如在card.tsx中
ts
export default async function CardWrapper() {
const {
numberOfInvoices,
numberOfCustomers,
totalPaidInvoices,
totalPendingInvoices,
} = await fetchCardData();
return (
<>
<Card title="Collected" value={totalPaidInvoices} type="collected" />
<Card title="Pending" value={totalPendingInvoices} type="pending" />
<Card title="Total Invoices" value={numberOfInvoices} type="invoices" />
<Card
title="Total Customers"
value={numberOfCustomers}
type="customers"
/>
</>
);
}
export function Card({
title,
value,
type,
}: {
title: string;
value: number | string;
type: 'invoices' | 'customers' | 'pending' | 'collected';
}) {
const Icon = iconMap[type];
return (
<div className="rounded-xl bg-gray-50 p-2 shadow-sm">
<div className="flex p-4">
{Icon ? <Icon className="h-5 w-5 text-gray-700" /> : null}
<h3 className="ml-2 text-sm font-medium">{title}</h3>
</div>
<p
className={`${lusitana.className}
truncate rounded-xl bg-white px-4 py-8 text-center text-2xl`}
>
{value}
</p>
</div>
);
}
就同时存在了这两种导出方式,同时card是cardWrapper数据的载体需要用到,如果你把 Card 挪到另一个文件,你也得在 cards.tsx 顶层写 import { Card } from './SingleCard'。既然目前这个 Card 只在这一处大规模使用,放在一起其实更清爽。
问:针对上一个问题,什么时候该拆分(这里的拆分其实就是组件应该写在app下还是写在具体的目录例如 invoice dashboard下,其实也就看能不能复用)?
答:要建立这种 拆分标准 。只有当以下情况发生时,你才应该把 Card 拿出去:
- 复用需求 :如果你的
Customers页面或Invoices页面也想用一模一样的卡片样式,那就拆。 - 复杂度爆表 :如果
Card的 CSS 代码写了 200 行,逻辑又变得很复杂,为了可读性,那就拆。
问:export const inter = Inter({ subsets: ['latin'] });是什么意思,又有什么用?
答:这里就是默认导出的写法,其中 Inter({ subsets: 'latin' })就是利用上面导入的Inter函数实现''我要使用 Inter 字体,只加载英文字符集,帮我生成一个配置好的字体对象''这个功能,最后把生成好的字体对象存到变量 inter 里并且把这个配置好的字体,暴露出去,让别的文件能用到。
问:
ts
<body className={`${inter.className} antialiased`}>{children}</body>
这段代码是什么意思啊?
答:在写法层面分析:类如${inter.className} antialiased结果是font-inter antialiased,这是一个具体的类名并且有逻辑含义所以用{},但是为了严谨font-inter不适合直接写。因为想要能自动适配字体加载、全局样式等逻辑,哪怕后续字体名变了,代码不用改;所以要用变量来转化,所以用${}来表示逻辑关系。
在逻辑层面分析:这里inter.className中的inter就是上面导入的默认导入,这里是为了提取这个inter的属性classname
2.图片优化:
1.主要作用者 ---Image
Image不是普通 img,它是自动帮你做图片优化的组件。 它自带这些强大功能:

这里注意需要写的属性:

其他的不是必要写的,只要写出Image标签他的图片就会自动优化

2.代码分析
ts
// /app/page.tsx
import AcmeLogo from '@/app/ui/acme-logo';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { lusitana } from '@/app/ui/fonts';
import Image from 'next/image';
export default function Page() {
return (
// ...
<div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
{/* Add Hero Images Here */}
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
</div>
//...
);
}.
问:在你这段代码里,它是怎么优化图片的?
答案:import Image from 'next/image'是默认导入next/image 是 Next.js 自带的图片优化工具 它里面是 export default Image所以你导入时 不用大括号,直接写名字就行
再来看看主要逻辑部分:
① src="/hero-desktop.png" 图片放在 /public 文件夹 / 开头就代表从 public 里找
② width={1000} height={760} 告诉 Next 图片原始宽高 它会自动保持比例,不会变形 关键:防止页面布局跳动(CLS)
③ className="hidden md:block" 这是 Tailwind 响应式: hidden → 手机上隐藏 md:block → 平板 / 电脑上显示
补充建议:响应式图片的"两套方案"
你在代码中用了 hidden md:block 来切换桌面端图片。这是最简单、最稳妥的做法。
但如果项目非常复杂,工程师有时会使用 sizes 属性或者 fill 模式:
fill模式 :当你不知道图片的具体宽高(比如图片由用户上传)时,给父元素设置position: relative,给 Image 设置fill属性,图片会自动撑满容器。
3.总结:

