Next 入门篇

项目初始化

手动创建 Next 项目

第一步:创建文件夹 next-demo
第二步:初始化 package.json

1
npm init -y

第三步;安装 next react

1
npm install next@latest react@latest react-dom@latest

第四步:package.json 添加执行命令

1
2
3
4
5
6
7
8
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}

第五步:新建 layout 文件 src/app/layout.js

1
2
3
4
5
6
7
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}

第六步:新建 page 文件 src/app/page.js

1
2
3
4
5
6
7
export default function Home() {
return (
<main>
<h1>Hello Next</h1>
</main>
);
}

然后执行 dev 命令就能启动项目了

通过 create-next-app 脚手架创建项目

1
npx create-next-app@latest

App Router

在 Next 13.4 版本后推荐使用 App Route 在上文通过 create-next-app 脚手架创建项目时,有个选项就是是否使用 App Router

页面

创建如上的目录结构,page.js 可以理解为对应的页面文件,app 下的 page.js 对应的路由就是 / about 文件下的 page.js 对应的路由就是 /about 如果目录下没有 page.js 文件,也就不会生成对应的路由

Layout 布局

Layout 相当于页面的“壳”,每个页面都会在 layout 下,另外还可以给每个页面单独设置一个 layout,例如在 about 目录下新建 layout.js 文件

最后呈现的效果大概是这样,Layout 就是最外层 app/layout.js About Page 是 about/layout.js

根目录的 layout

根目录下的 layout 有一点特殊,首先这个文件是必需的,另外这个文件必须有 html 和 body 标签,而其他的 layout 不能有

模板 template

模板和 layout 的区别在于 template 没有办法保持状态。

目录结构

这里 detail 和 detail2 只是用来进行跳转用,重点看一下 layout 和 template

layout.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
'use client'
import {useState} from "react";
import Link from "next/link";

export default function ProductLayout({children}) {
const [state, setState] = useState(0)
return (
<>
Layout: {state}
<br/>
<button onClick={() => setState(state+1)}>add</button>
{children}
<br/>
<Link href="/product/detail">TO DETAIL</Link>
<br/>
<Link href="/product/detail2">TO DETAIL2</Link>
</>
)
}

template.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'use client'
import {useState} from "react";

export default function ProductLayout({children}) {
const [state, setState] = useState(0)
return (
<>
Template: {state}
<br/>
<button onClick={() => setState(state+1)}>add</button>
{children}
</>
)
}

在 layout 和 template 中分别加入状态,当我们切换路由时看状态变化

效果图

加载 loading

在页面同级下创建 loading.js,以上例子是 about/loading.js

1
2
3
export default function DashboardLoading() {
return <>Loading...</>
}

然后在 about/page.js 中

1
2
3
4
5
6
7
8
9
10
export default async function About() {
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 3000))
return {
data: 'About Page',
}
}
const { data } = await getData();
return <h1>{data}</h1>
}

这时再进入到 about 页面就会先出现 Loading… 等到 page.js 内的请求完毕 loading 关闭,所以 page 必须是一个异步函数,或者使用 React 的 use 函数才能触发 loading

1
2
3
4
5
6
7
8
9
10
export default function About() {
async function getData() {
await new Promise((resolve) => setTimeout(resolve, 3000))
return {
data: 'About Page',
}
}
const { data } = React.use(getData());
return <h1>{data}</h1>
}

loading 会给 page 包一层 <Suspense> 用来捕获 page 加载的 promise,实现 loading 效果。

错误 error

通过 error 可以在页面发生错误时,展示 error

还是页面的同级目录创建 error.js,另外需要注意的是 error 组件必须是客户端组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'use client'
import { useEffect } from 'react'

export default function Error({ error, reset }) {
useEffect(() => {
console.error(error)
}, [error])

return (
<div>
<h2>Something went wrong!</h2>
<button
onClick={
// 尝试恢复
() => reset()
}
>
Try again
</button>
</div>
)
}

error 会给 page 包一层 <ErrorBoundary>

全局错误处理 global-error

404页面 not-found

next 存在默认的 404 页面
next-404

如果想要自定义 404 页面,可以在 app 目录下新增一个 not-found.js 作为 404 页面。

跳转 404

next 有两种方式可以触发 not-found,第一种是执行 notFound 函数,第二种是路由找不到的情况。

1
2
3
4
5
6
7
import { notFound } from 'next/navigation'

export default async function Index() {
notFound()

// ...
}

层级关系

文件结构

层级结构:

1
2
3
4
5
6
7
8
9
10
11
<Layout>
<Tempolate>
<ErrorBoundary fallback={{<Error/>}}>
<Suspense fallback={<Loading/>}>
<ErrorBoundary fallback={<NotFound/>}>
<Page />
</ErrorBoundary>
</Suspense>
</ErrorBoundary>
</Tempolate>
</Layout>

Next 中的懒加载

React 提供了 lazy + Suspense 实现了懒加载。在 Next 中额外提供了 next/dynamic 方式实现懒加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
'use client'

import { useState } from 'react'
import dynamic from 'next/dynamic'

// Client Components:
const ComponentA = dynamic(() => import('../components/A'))
const ComponentB = dynamic(() => import('../components/B'))
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })

export default function ClientComponentExample() {
const [showMore, setShowMore] = useState(false)

return (
<div>
{/* Load immediately, but in a separate client bundle */}
<ComponentA />

{/* Load on demand, only when/if the condition is met */}
{showMore && <ComponentB />}
<button onClick={() => setShowMore(!showMore)}>Toggle</button>

{/* Load only on the client side */}
<ComponentC />
</div>
)
}

使用时需要注意的是

  1. import 中不能是模板字符串和变量
  2. dynamic 要在模块顶层

客户端组件默认会被预渲染(SSR),通过 ssr: false 可以关闭预渲染,这会让组件比正常预渲染的组件晚显示,实际上会有一个 template 给该组件占位,然后等待加载完成再渲染。

关于导入导出

1
2
3
export function Hello() {
return <p>Hello World</p>
}

在使用 Next 的懒加载时,如果需要导入以上这种非默认导出需要:

1
2
3
4
5
import dynamic from 'next/dynamic'

const ClientComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)

Next 入门篇
https://l1ushun.github.io/2024/08/08/next-01/
作者
shun
发布于
2024年8月8日