Next.js 14 App Router 海底捞针我帮你做了!(上)


theme: condensed-night-purple

1. 前言

Next.js 14 提供了新的 App Router 作为默认的路由方案,文件夹的嵌套结构决定了路由的渲染或请求的返回处理。太棒了这逻辑清晰,再也不必手动配置 router 了,开发一路畅通了,家人们。

这简直就是危言耸听!那文档翻起来无异于海底捞针。

简直危言耸听.png

本文分为上下两篇,上篇主要以路由为核心,下篇补充网络请求以及各杂项。

2. 路由页面

例如有以下路由页面:

| 访问路径 | 页面组件 | 名称 | | ----------------- | -------------------------- | ------------ | | / | app/page.js | 主页 | | /blogs | app/blogs | 文章列表 | | /blogs/1 | app/blogs/[id]/page.js | 文章详情(使用id) | | /blogs/hello-next | app/blogs/[slug]/page.js | 文章详情(使用slug) | | /login | app/(auth)/login/page.js | 登录 | | /forget-password | app/(auth)/forget-password | 忘记密码 |

登录和忘记密码使用(auth)进行逻辑分组,不影响路径访问。

在路由渲染方面,还提供了诸如 layout.js、loading.js、error.js、not-found.js 以配置布局页面、加载页面、错误反馈页面、404页面。

3. 路由处理器

除了页面渲染,还有路由处理器(Route Handler)。笔者在使用这部分特性时摔过不少跟头,它不如 Pages Router 的 API Routes 那么直观。

假设有以下 API 接口:

  • GET api/v1/blogs
  • GET api/v1/blogs/1
  • POST api/v1/login { email, password }
  • GET api/v1/blogs?q=xxx
  • GET api/v1/blogs?q=xxx&\&start=1&\&end=2

在 route.js 中定义路由处理器:

```tsx export async function GET(request) {}

export async function HEAD(request) {}

export async function POST(request) {}

export async function PUT(request) {}

export async function DELETE(request) {}

export async function PATCH(request) {}

// 如果 OPTIONS 没有定义, Next.js 会自动实现 OPTIONS export async function OPTIONS(request) {}

```

每一个方法都有 request 参数,从中可以解析出所需的内容。请求对象的类型可以是 Request 或者 NextRequest,后者是扩展了前者功能的类型。在实际开发中,笔者使用的是后者。同理,响应对象的类型有 ResponseNextResponse

3.1 body

body 数据藏在 request.json() 中。

```tsx // app/api/login/route.ts // POST api/v1/login { email, password } export async function POST(request: NextRequest) { // 1. 获取 body 内容 const body = await request.json(); const {email, password} = body

// 2. 请求数据 // ...

// 3. 返回响应体 return NextResponse.json({ success: true, msg: '登录成功', data: null }, { status: 200, headers: { 'Set-Cookies': token=${token};path=/;max-age=86400;HttpOnly, } }) } ```

3.2 query && pathname

查询字符串藏在 request.nextUrl.searchParams 中。

  1. 如果是单个查询字符串:

tsx // app/api/blog/route.ts // GET api/v1/blogs?q=xxx export function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams const q = searchParams.get('q') // 'xxx' // ... }

  1. 如果是多个查询字符串:

tsx // app/api/blog/route.ts // GET api/v1/blogs?q=xxx&&start=1&&end=2 export function GET(request: NextRequest) { const searchParams = request.nextUrl.searchParams const q = searchParams.get('q') // 'xxx' const start = searchParams.get('start') // '1' const end = searchParams.get('end') // '2' // ... }

pathname 藏在 request.nextUrl.pathname 下:

tsx // 如果访问 /home,该值为 /home request.nextUrl.pathname

3.3 params

如果想要获取路由的动态参数 [id] 或 [slug],咋办?

这些数据就藏在路由处理器中的第二个参数中:

```tsx // app/api/blog/[id]/route.ts // GET api/v1/blogs/1 export async function GET(request: NextRequest, { params }: {params: {id: string}}) { const id = params.id // ... }

// app/api/blog/[slug]/route.ts // GET api/v1/blogs/hello-next export async function GET(request: NextRequest, { params }: {params: {slug: string}}) { const slug = params.slug // ... } ```

3.4 cookies

cookie 在用户登录时在响应豹纹中返回给前端,这在《图解HTTP》书中有形象的描述:

cookie示意图.png

对应的豹纹如下:

HTTP报文.png

可以看到,响应豹纹中通过 Set-Cookie 设置了 sid,以后客户端请求中自动携带了 Cookie,里面就放着 sid 数据。同理,cookie 里可以放 token。

有的接口需要把 token 信息传给服务器,因此获取 cookie 就变得尤为重要。在请求头和响应头中都可以获取到 Cookie,这里以请求头为例,利用 request.cookies.get('token')

tsx // app/api/blog/route.ts // GET api/v1/blogs export async function GET(request: NextRequest) { const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${request.cookies.get('token')?.value}`, }, }); // 除了 200-299 之间的状态码都会视为失败 if (response.ok) { const data = await response.json(); return NextResponse.json({data}); } return NextResponse.json('请求失败', {status: 500}); }

如上,拿到 token 的值设置到请求头参数 Authorization 中。

next/headers 可以得到 cookie 方法, 删除的方式如下:

```tsx // app/api/logout/route.ts import { NextResponse } from 'next/server'; import { cookies } from 'next/headers';

export async function DELETE() { // 设置过期时间为0来删除cookie cookies().set('token', '', { maxAge: 0 });

return NextResponse.json({ success: true, msg: '登出成功', }); } ```

3.5 headers

在 3.1 中可以看到,在返回 json 数据时可以设置 headers。

如果想要获取 headers 信息(只读),从 next/headers 可以得到 headers 方法:

```tsx import { headers } from 'next/headers'

export async function GET(request: Request) { const headersList = headers() // ... } ```

3.6 重定向

```tsx import { redirect } from 'next/navigation'

export async function GET(request: Request) { redirect('https://nextjs.org/') } ```

4. 在服务端组件中获取 URL 参数

在路由处理器中,可以从 request 中拿到很多东西,而到了服务端组件是没有 request 对象的。

在 page.js 中呈现的是某一路由的组件,从 props 中可以解构出 params 和 searchParams:

tsx // app/blog/[slug]/page.tsx export default function Page({ params, searchParams, }: { params: { slug: string } searchParams: { [key: string]: string | string[] | undefined } }) { return <h1>My Page</h1> }

5. 在客户端组件中获取 URL 参数

以上是在服务端组件中获取参数的方式,而在客户端组件中利用客户端 hook 获取。

5.1 useRouter()

控制路由跳转、重定向等。

```tsx 'use client'

import { useRouter } from 'next/navigation'

export default function Page() { const router = useRouter()

// ... } ```

  • router.push(href: string, { scroll: boolean }) :对提供的路由执行客户端导航。在浏览器的历史堆栈中添加一个新条目。(可以用<Link> 组件代替。)
  • router.replace(href: string, { scroll: boolean }): 执行指向所提供路由的客户端导航,但不在浏览器历史堆栈中添加新条目。
  • router.refresh():刷新当前路由。向服务器发出新请求、重新获取数据请求并重新渲染服务器组件。客户端将合并更新的 React 服务器组件有效载荷,而不会丢失未受影响的客户端 React(如useState)或浏览器状态(如滚动位置)。
  • router.prefetch(href: string): 预取所提供的路由,以加快客户端转换。
  • router.back():返回浏览器历史堆栈中的前一个路由。
  • router.forward():向前导航至浏览器历史堆栈中的下一页。

5.2 usePathname()

获取 URL 上的 pathname,跟路径后面那一串,不包含查询字符串。

```tsx 'use client'

import { usePathname } from 'next/navigation'

export default function ExampleClientComponent() { const pathname = usePathname()

// URL -> /dashboard?search=my-project // pathname -> 'dashboard'

// URL -> /dashboard/overview // pathname -> '/dashboard/overview'

// ... } ```

5.3 useSearchParams()

获取 URL 上的查询字符串。

```tsx 'use client'

import { useSearchParams } from 'next/navigation'

export default function SearchBar() { const searchParams = useSearchParams()

const search = searchParams.get('search')

// URL -> /dashboard?search=my-project // search -> 'my-project'

// ... } ```

5.4 useParams()

获取 URL 上的路由参数。

```tsx 'use client'

import { useParams } from 'next/navigation'

export default function ExampleClientComponent() { const params = useParams<{ tag: string; item: string }>()

// Route -> /shop/[tag]/[item] // URL -> /shop/shoes/nike-air-max-97 // params -> { tag: 'shoes', item: 'nike-air-max-97' }

// ... } ```

5.5 redirect()

在客户端组件中,无法直接使用 redirect(path, type) 方法,它的使用范围是:Server ComponentsRoute Handlers 以及 Server Actions

使用 useRouter() 相关方法代替或者这个🌰:https://nextjs.org/docs/app/api-reference/functions/redirect#client-component

6. 服务端组件和客户端组件的组合模式

服务端组件和客户端组件可以嵌套组合使用,但有所限制,它们各自的使用时机也不同。

6.1 服务端组件和客户端组件的使用时机

| 如果你想… | 服务端组件 | 客户端组件 | | -------------------------------------------------- | ----- | ----- | | 请求数据 | ✅ | ❌ | | 直接获取后端资源 | ✅ | ❌ | | 保持服务器中的敏感数据(获取 token、API key等等) | ✅ | ❌ | | 在服务器上保留大量依赖关系/减少客户端 JavaScript | ✅ | ❌ | | 添加交互性和事件监听器(onClick()、onChange()等) | ❌ | ✅ | | 使用状态和生命周期副作用(useState()、useReducer()、useEffect()等) | ❌ | ✅ | | 使用浏览器专用 API | ❌ | ✅ | | 使用依赖于状态、副作用或浏览器专用 API 的自定义钩子 | ❌ | ✅ | | 使用 React Class 组件 | ❌ | ✅ |

6.2 不支持的模式:服务端组件作为模块引入客户端组件

下面代码中,将服务端组件作为模块嵌入客户端组件是不可行的:

```tsx 'use client'

// You cannot import a Server Component into a Client Component. import ServerComponent from './Server-Component'

export default function ClientComponent({ children, }: { children: React.ReactNode }) { const [count, setCount] = useState(0)

return ( <>

// ❌ 错误
  <ServerComponent />
</>

) } ```

6.3 支持的模式:服务端组件放入客户端插槽

作为 props,比如从插槽中将服务端组件插入客户端则是正确的:

```tsx // You can pass a Server Component as a child or prop of a Client Component. import ClientComponent from './client-component' import ServerComponent from './server-component'

// Pages in Next.js are Server Components by default export default function Page() { return ( // ✅ 正确 ) } ```

🌰(全局主题设置):https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#using-context-providers


技术交流:

  • 公众号:见嘉 Being Dev
  • v:withhisx
  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值