Create a Next.js App
pnpm create next-app@13.4
CSS没有代码提示
当VSCode安装了PostCSS Language Support这个extension后,会导致.css
文件的代码提示功能失效,解决方法把css的Language mode换成tailwindcss。最好把这个设置配置在WorkSpace Settings中。
"files.associations": {
"*.css": "tailwindcss"
},
Unknown at rule @tailwindcss(unknownAtRules) · tailwindlabs tailwindcss · Discussion #13881
Routing
app routes和pages routes有一个很明显的区别,就是pages routes文件夹中的所有文件都会对外暴露,但是app routes不会,它只会暴露特定的文件,比如page.tsx
。
假设有这样的文件结构:
app/users
├── new
│ └── page.tsx
├── page.tsx
└── test.css
只有page.tsx
对外暴露,test.css
没有。
因为这一点使用app router便可以将自己的组件放到到/app/components
中了,只要组件名不是那些特定的文件名便不会被暴露。
Navigation - Link
在HTML中使用<a>
跳到其他路由会导致页面的刷新,页面刷新会带来不好的用户体验,可以使用Next.js提供的<Link>
组件完成路由调整
<main>
<div>Hello World</div>
<Link href="/users">Users</Link>
</main>
Client Components and Server Components
Client Components VS Server Components
-
Large bundles → Smaller bundles
-
Resource intensive, Resource Heavy → Resource efficient
-
No SEO → SEO
-
Less secure → More secure
Sensitive Data we have in our components or their dependencies, Like API keys, will be exposed to the client.
上面是Server Components的好处,但是Server Components也有缺点,下面是Server Components不能做的,这些功能只有Client Component能做:
-
Listen to browser events
比如,Server Components无法给
<button>
绑定事件 -
Access browser APIs
-
Maintain state
-
Use effects
In real world applications, we often use a mixture of server and client components.
比如下面的一些组件:
- Navbar
- Sidebar
- ProductList
- ProductCard
- Pagination
- Footer
这些Components在 Next.js 项目中都可以定义为Server Components,而且Next.js中所有组件默认都是Server Components。但是这导致问题,我们一定会希望某些组件处理事件的,这是一个网址最基本的功能,怎么做呢?
比如,这里有个ProductCard component:
function ProductCard() {
return (
<div>
<button onClick={() => {
console.log("clicked")
}}>Click</button>
</div>
)
}
export default ProductCard
这段代码会导致下面的错误:
Unhandled Runtime Error
[ Server ] Error: Event handlers cannot be passed to Client Component props.
<div onClick={function onClick} children=...>
^^^^^^^^^^^^^^^^^^
If you need interactivity, consider converting part of this to a Client Component.
解决方式有两种
第一种直接将整个Component变成Client Component,直接在最上面添加"use client"
,这个组件就变成了Client Component了:
'use client'
function ProductCard() {
return (
<div>
<button onClick={() => {
console.log("clicked")
}}>Click</button>
</div>
)
}
export default ProductCard
这通常不是好的做法,因为将整个组件变成了Client Component之后,便失去了所有Server Component的好处了,就回到了之前写React的样子,对于Client Component还有一点需要注意,当一个组件是Client Componen后,其有文件依赖的子组件将全部变成Client Component(这一点我说的很微妙,我会另外开一篇文章讲这个)。
好的做法是仅仅在必须使用Client Component的地方使用,所以这里可以只将<button>
另外提取出一个Component。然后再放到ProductCard中:
// /components/AddToCard.tsx
'use client'
function AddToCard() {
return (
<button onClick={() => {
console.log("Added to cart")
}}>Add to cart</button>
)
}
export default AddToCard
// /components/ProductCard.tsx
import AddToCard from "@/app/components/AddToCard"
function ProductCard() {
return (
<div>
<AddToCard />
</div>
)
}
export default ProductCard
Data Fetching
Fetch Data有两种方式,一种是在Client Side,还有一种是在Server Side。
Client Side去Fetch Data的方式,通常是先使用useState去declare要fetch的Data,然后在useEffect中去fetch,拿到后再存到state中,当然也可以用其他的Tools,像React Query。
Client Side Data fetching的缺点很明显,就是会多一层Fetch,我们总是先拿到一个没有数据的Component,然后再发送一个请求数据(Extra roundtrip to server),然后再执行render。这个方式无论是时间消耗或者内存消耗都会比Server Side Data Fetching更多。而且我觉得Server Side Data Fetch更简单。
下面是个Server Side Data Fetching,使用很多人知道的公共API, JSON placeholder请求users
interface User {
id: number;
name: string;
}
async function UsersPage() {
const response = await fetch("<https://jsonplaceholder.typicode.com/users>");
const users: User[] = await response.json();
return (
<div>
<h1>Users</h1>
<ul>
{users.map((user) => (
<div key={user.id}>{user.name}</div>
))}
</ul>
</div>
);
}
export default UsersPage;
可以看到我们不需要useState也不需要useEffect,可以直接在组件中请求数据,然后将数据展示出来,可以看到浏览器直接拿到这样有数据的component。
Caching
Server Side Fetching还有一个好处,就是可以Caching,对于一个Web application,获取数据有以下方式:
- Memory
- File System
- Network
从上往下的请求速度变慢,Caching是将原本Network中的Data存到Memory中,Next.js扩张了fetch函数,默认会cache相同url产生的数据,这个数据再构建的时候便产生了,后续如果数据更新,不会看到数据的更新。这是其中的一种缓存策略,还有其他两种常用的的缓存策略,需要配置fetch:
const response = await fetch("<https://jsonplaceholder.typicode.com/users>", {
cache: "no-store",
}); // 不缓存任何数据
const response = await fetch("<https://jsonplaceholder.typicode.com/users>", {
next: {
revalidate: 10,
}
}); // 请求产生生成10s内不再请求数据
Static and Dynamic Rendering
Static Page在构建时就把数据写死,以后请求都将是相同的数据,比如
<div>{new Date().toLocaleTimeString()}</div>
这个tag,如果当前page是个static page,那么后续请求的数据都只是构建时的数据。
那么,问题来了,Next.js是如何确定一个Page是不是Static Page的?
在开发模式下,所有的数据都是Dynamic的,但是到生产环境不一定,生产环境某些pages被Next.js视为Static的,比如这个page中使用了缓存的fetch,这个Page将是static的。
Rendering in Next.js
一个页面在Next.js中被展示出来,上面描述的那些方式,下图将这些方式总结出来:
Reference
本笔记来自油管博主Code with mosh的Next.js13课程: