NextJs 初级篇 - 安装 | 路由 | 中间件

news2024/12/27 13:26:14

NextJs 初级篇 - 安装 | 路由 | 中间件

  • 一. NextJs 的安装
  • 二. 路由
    • 2.1 路由和页面的定义
    • 2.2 布局的定义和使用
    • 2.3 模板的定义和使用
      • ① 模板 VS 布局
      • ② 什么是 use client
    • 2.4 路由跳转的方式
    • 2.5 动态路由
    • 2.6 路由处理程序
      • ① GET 请求的默认缓存机制
      • ② 控制缓存或者退出缓存的手段
      • ③ 控制缓存的时效 revalidate
      • ④ 常见的编写问题
  • 3. 中间件

一. NextJs 的安装

首先,NextJs要想使用,node版本不能太低,最低也要18以上,版本不够的,可以用nvm来管理,Mac用户的安装可以参考这篇文章:
mac 系统正确安装nvm

版本满足的同学,建议直接使用脚手架来创建nextJs,输入脚手架命令(这里又切了阿里的镜像,以防万一关闭SSL认证):

npm config set registry https://registry.npm.taobao.org
npm config set strict-ssl false
npx create-next-app@latest

结果如下:这里我们优先使用App Router(后面会讲解)
在这里插入图片描述
生成结果如下:
在这里插入图片描述
启动项目: npm run dev,之后访问:http://localhost:3000/ 即可

二. 路由

NextJs 拥有两套路由(两者兼容):

  • Pages Router:在pages目录下创建对应的文件或者目录即是一个路由。
  • App RouterNextJs从版本13.4起的默认路由模式

为什么官方在新版本中,默认的路由模式采用了App呢?

  • pages每个文件都会被当成路由,不符合开发习惯
  • app 架构新增了布局(layout)、模版(template)、加载状态(loading)、错误处理(error)、404 等文件,为项目开发提供了一套规范。

2.1 路由和页面的定义

我们这里主要讲官方更推荐的AppRouter,如图:
在这里插入图片描述
这里简单介绍下:

  • 我们约定使用 page 来代表一个页面,就好比React中使用:index。和React一样,默认导出个组件即可。
  • 文件的路径就是对应的路由。
  • app/page.tsx 对应路由 /
  • app/about/page.tsx 对应路由 /about
  • app/address/page.tsx 对应路由 /address
  • 后缀名可以使用:.js、.jsx、.tsx

2.2 布局的定义和使用

首先布局组件有这么几个特征:

  • 定义一个布局,需要新建一个名称为:layout 的固定文件,该组件接收一个children,代表子页面或者子布局。
  • 布局可以嵌套,父布局中有一个子布局。

根布局还有额外几个特征:

  • 定义在app/layout.tsx文件中,会应用于所有的路由。并且此文件必须存在。
  • 根布局文件中必须包含htmlbody标签。同时其他布局不能包含这些标签。

根布局文件内容示例:

import { Inter } from "next/font/google";
import "./globals.css";
const inter = Inter({ subsets: ["latin"] });
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  );
}

布局的嵌套: address 下有个子目录home,每个文件都有自己的layout布局。
在这里插入图片描述
AddressLayout

const AddressLayout = ({ children }: any) => {
    return (
        <>
            <nav>
            我是Address的Layout
            </nav>
            {children}
        </>
    )
}
export default AddressLayout

HomeLayout

const HomeLayout = ({ children }: any) => {
    return (
        <>
            <nav>
            我是Home的Layout
            </nav>
            {children}
        </>
    )
}
export default HomeLayout

访问http://localhost:3000/address/home,呈现效果:
在这里插入图片描述

2.3 模板的定义和使用

模板跟布局的用法是一样的,例如我们在address下定义一个固定名称的模板文件:template.tsx

const AddressTemplate = ({ children }: any) => {
    return (
        <>
            <nav>
            我是Address的模板
            </nav>
            {children}
        </>
    )
}
export default AddressTemplate

此时我们访问 http://localhost:3000/address/home,呈现效果:在这里插入图片描述
这说明了:layout 会包裹 templatetemplate 再包裹 page

那模板和布局之间,存在着什么差异?

① 模板 VS 布局

状态的维持:

  • 模板:路由切换的时候,模板的内容会随之变更。
  • 布局:路由切换的时候,布局的内容不会更改。

框架默认行为的更改:以组件Suspense为例,这个组件就是一个渲染fallback组件效果的功能。

  • 模板:模板内使用Suspense,每次切换路由的时候,都会展示fallback组件
  • 布局:模板内使用Suspense,每次切换路由的时候,只会展示一次。

其实这两个点比较类似,说白了就是,每次在切换路由的时候,使用 template ,就可以重新触发渲染。

来个案例,目录结构如下:
在这里插入图片描述
sonA

const SonA = () => {
    return <>
        我是SonA!!!
    </>
}
export default SonA

sonB

const SonB = () => {
    return <>
        我是SonB!!!
    </>
}
export default SonB

父组件:page.tsx

import Link from "next/link";

const Parent = () => {
    return <>
        <nav>
            当前页面是父组件
        </nav>
        <nav>
            跳转到:<Link href='/parent/sonA'>SonA</Link>
        </nav>
        <nav>
            跳转到:<Link href='/parent/sonB'>SonB</Link>
        </nav>
    </>
}

export default Parent

template.tsx

'use client'
import { useState } from "react"
const AddressTemplate = ({ children }: any) => {
    const [count, setCount] = useState<number>(0);
    return (
        <>
            <h1>Template点击次数: {count}</h1>
            <button onClick={() => setCount(count + 1)}>
                点击
            </button>
            {children}
        </>
    )
}
export default AddressTemplate

layout.tsx

'use client'
import { useState } from "react"
const ParentLayout = ({ children }: any) => {
    const [count, setCount] = useState<number>(0);
    return (
        <>
            <h1>Layout点击次数:  {count}</h1>
            <button onClick={() => setCount(count + 1)}>
                点击
            </button>
            {children}
        </>
    )
}
export default ParentLayout

结果如下:

在这里插入图片描述

② 什么是 use client

'use client' 是一个指令,放置在文件的顶部(通常是模块的第一行)。它告诉 Nextjs 该模块应该在客户端环境中执行,而不是在服务器端。

备注:默认情况下,NextJs中的页面是由服务端渲染的,即SSR

2.4 路由跳转的方式

在上述案例中,我们使用 Link 组件来完成路由的跳转。而NextJs中,一共支持3种路由跳转的方式:

  • Link 组件。
  • 使用钩子函数:useRouter
  • 使用 redirect 函数

第一种 Link 组件,是最为推荐的方式,例如上述案例中的代码。

<Link href='/parent/sonA'>SonA</Link>

除此之外,还可以更改默认的跳转行为:App Router 的默认行为是滚动到新路由的顶部。可以通过传参 scrollfalse改变。

<Link href='/parent/sonA' scroll={false}>SonA</Link>

第二种使用useRouter函数:

'use client'
import Link from "next/link";
import { useRouter } from 'next/navigation'

const Parent = () => {
    const router = useRouter();
    return <>
        <nav>
            当前页面是父组件
        </nav>
        <nav>
            跳转到:<Link href='/parent/sonA'>SonA</Link>
        </nav>
        <nav>
            跳转到:<button onClick={() => router.push('/parent/sonB')}>SonB</button>
        </nav>
    </>
}

export default Parent

我们还能发现,一旦使用钩子函数,这种客户端触发的动作,就需要加上标识'use client',代表它是需要客户端渲染。


第三种:redirect,用于给服务端组件用的,伪代码如下:

import { redirect } from 'next/navigation'
 
async function login(id) {
  const res = await login()
  if (!res.ok) {
     redirect('/error')
  }
  return res.json()
}

2.5 动态路由

假设我们访问某个订单详情页,订单号可能是动态会变的,这里假设订单号是123456,一般我们可以设计两种URL

  • /orderInfo/123456
  • /orderInfo?orderId=123456

先说第一种,就需要通过动态路由的方式来访问。NextJs中,需要将文件夹的名字用方括号[] 扩住。例如我们创建一个目录:orderInfo/[orderId],代表orderId是一个动态路由,其中这个动态参数可以通过orderId字段来读取。
在这里插入图片描述
然后页面上我们希望把订单号展示出来。其中组件接收一个参数:params,如下:

import React from 'react'
const About = ({ params }: any) => {
    return <>
        <h1>订单详情页面</h1>
        <span>{params.orderId}</span>
    </>
}
export default About

那么在访问 http://localhost:3000/orderInfo/123 的时候,效果如下:
在这里插入图片描述


那如果我们动态参数后还跟着路由,例如 http://localhost:3000/orderInfo/123/shop 若直接访问我们看看结果:
在这里插入图片描述
我们发现此时404了,可见这种情况下,动态参数[orderId]只会接收第一个路由片段,那怎么办呢?我们可以将[orderId] 改为: [[..orderId]],表示捕获所有后面所有的路由片段。
在这里插入图片描述
例如:
在这里插入图片描述

2.6 路由处理程序

NextJs中,对于客户端和服务端之间的API交互,叫做路由处理程序。

编写一个简单的路由处理程序,规则如下:

  • 接口的路径跟路由比较相似,对应文件目录下创建一个名为 route 的文件即可。
  • 文件中需要导出固定名称的函数(无需使用default exportGET、POST等。
  • 可以使用NextResponse代表返回的数据。
// route.js
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) {}

一个简单的GETPOST请求:
在这里插入图片描述
GET请求:

import { NextRequest, NextResponse } from 'next/server'

export async function GET() {
    const data = [{ id: 1, name: 'LJJ' }]
    return NextResponse.json({ data })
}

对应结果:
在这里插入图片描述
POST请求:

import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
    const data = await request.json();
    return NextResponse.json(data)
}

对应结果:
在这里插入图片描述

① GET 请求的默认缓存机制

默认情况下,使用NextResponse对象的GET请求会被NextJs缓存起来。(本地模式下不会缓存,生产模式则会)

我们改一下我们的getUserList接口:

import { NextResponse } from 'next/server'

export async function GET() {
    const data = [{ id: 1, name: 'LJJ', date: new Date().getMilliseconds() }]
    return NextResponse.json({ data })
}

然后跑命令: npm run build & npm run start
我们能发现构建产物:/api/getUserList 是静态的。
在这里插入图片描述
我们请求接口发现:无论请求几遍,时间戳永远不会变,是因为被缓存了。
在这里插入图片描述

② 控制缓存或者退出缓存的手段

退出缓存的方式有这么几种:

  • GET 请求中使用了 Request 对象。例如:返回对象依赖于Request的参数。
  • 添加其他 HTTP 方法,比如同一个route文件中,同时定义了GETPOST函数。
  • 使用像 cookiesheaders 这样的动态函数。
  • 也可以手动声明接口为动态模式。 文件顶端增加:export const dynamic = 'force-dynamic' 即可
// 返回结果依赖于request的参数
export async function GET(request: NextRequest) {
    const searchParams = request.nextUrl.searchParams
    const data = [{ id: 1, name: searchParams.toString(), date: new Date().getMilliseconds() }]
    return NextResponse.json({ data })
}
// 使用cookies这种动态函数
export async function GET(request) {
  const token = request.cookies.get('token')
  return Response.json({ data: new Date().toLocaleTimeString() })
}

③ 控制缓存的时效 revalidate

我们可以在route文件的顶部增加声明,例如以下就是标识缓存的有效期为10秒。

export const revalidate = 10

④ 常见的编写问题

首先是:获取URL上的参数、获取Header、获取Request请求体(JSON)、Cookie

import { NextRequest, NextResponse } from 'next/server'
export async function POST(request: NextRequest) {
    // 获取URL上的参数
    const searchParams = request.nextUrl.searchParams
    console.log(searchParams.get('orderId'));
    // 处理 Header
    const headersList = new Headers(request.headers)
    const referer = headersList.get('myHeader')
    console.log(referer)
    // 获取Request请求体 (JSON)
    const data = await request.json();
    // 如果是FormData
    // const formData = await request.formData()
    // const name = formData.get('name')
  
    return NextResponse.json(data)
}

Postman请求:http://localhost:3000/api/getDetail?orderId=123
在这里插入图片描述
结果如下:
在这里插入图片描述


重定向:使用redirect即可。

import { redirect } from 'next/navigation'

export async function POST(request: NextRequest) {
    redirect('https://www.baidu.com')
}

获取Cookie、并设置到Response中,则需要返回一个使用 Set-Cookie headerResponse 实例

import { cookies } from 'next/headers'
 
export async function POST() {
  const cookieStore = cookies()
  const token = cookieStore.get('test')
  console.log(token)
 
  return new Response('OK', {
    status: 200,
    headers: { 'Set-Cookie': `myNewToken=${12312312312}` },
  })
}

请求后:
在这里插入图片描述

3. 中间件

NextJs 中,中间件的定义,可以在根目录中定义一个固定名称的文件:middleware.ts,它的内容有两个部分组成:

// middleware.js
import { NextResponse } from 'next/server'
 
// 第一部分:导出固定名称的中间件
export function middleware(request) {
  // ... 中间件逻辑
}
// 第二部分:设置匹配路径
export const config = {
    matcher: '/api/:path*',
}

这里直接给个案例,假设我中间件有多个功能:

  • 中间件 headers:处理相关的Header
  • 中间件 logging:记录相关的日志

我们可以在根目录下创建个专门编写中间件的文件夹:middlewares
在这里插入图片描述
两个中间件内容:

// logging.ts
import { NextRequest } from "next/server";
export const logging = (next: any) => {
    return async (request: NextRequest) => {
        // ...
        console.log('logging')
        return next(request);
    };
};
// headers.ts
import { NextRequest } from "next/server";
export const headers = (next: any) => {
    return async (request: NextRequest) => {
        console.log('headers')
        return next(request);
    };
};

然后我们可以设置下别名,tsconfig.json 文件中:
在这里插入图片描述
这样就可以在真正设置中间件的地方(根路径的middleware.ts中)进行设置:

import { logging } from "@/middlewares/logging";
import { headers } from "@/middlewares/headers";
import { NextResponse } from "next/server";

function chain(functions: any, index = 0) {
    const current = functions[index];
    if (current) {
        const next: any = chain(functions, index + 1);
        return current(next);
    }
    return () => NextResponse.next();
}

export default chain([logging, headers]);

export const config = {
    matcher: '/api/:path*',
}

这样访问任何一个接口,都会走相关的逻辑了。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1698028.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IntelliJ IDEA实用插件:轻松生成时序图和类图

IntelliJ IDEA生成时序图、类图 一、SequenceDiagram1.1 插件安装1.2 插件设置1.3 生成时序图 二、PlantUML Integration2.1 插件安装2.2 插件设置2.3 生成类图 在软件建模课程的学习中&#xff0c;大家学习过多种图形表示方法&#xff0c;这些图形主要用于软件产品设计。在传统…

OFDM 802.11a的FPGA实现(二十)使用AXI-Stream FIFO进行跨时钟(含代码)

目录 1.前言 2.AXI-Stream FIFO时序 3.AXI-Stream FIFO配置信息 4.时钟控制模块MMCM 5.ModelSim仿真 6.总结 1.前言 至此&#xff0c;通过前面的文章讲解&#xff0c;对于OFDM 802.11a的发射基带的一个完整的PPDU帧的所有处理已经全部完成&#xff0c;其结构如下图所示&…

澳大利亚.德国-新闻媒体投放通稿:发表新闻稿需要留意哪些地方-大舍传媒

概述 当我们想要发布新闻稿时&#xff0c;了解目标媒体的特点和要求是至关重要的。本文将介绍澳大利亚和德国的新闻媒体&#xff0c;以及在撰写和投放新闻稿时需要注意的要点&#xff0c;以帮助您更好地与目标受众沟通。 澳大利亚媒体 澳大利亚是一个多元化的国家&#xff0…

C语言PTA练习题(编程求1+3+5+...+n,编程求n的阶乘,爬楼梯,爬楼梯(扩展),猴子吃桃,猴子吃桃(扩展),求算式前n项的和,汉诺塔游戏)

7-1 编程求135...n 输入一个正整数n&#xff0c;编程求135...n 要求&#xff1a;用递归完成。 输入格式: 输入一行&#xff0c;只有一个整数n (1<n<9999) 这里n为奇数。 输出格式: 一个正整数&#xff0c;表示结果。 输入样例: 在这里给出一组输入。例如&#xf…

【机器学习-23】关联规则(Apriori)算法:介绍、应用与实现

在现代数据分析中&#xff0c;经常需要从大规模数据集中挖掘有用的信息。关联规则挖掘是一种强大的技术&#xff0c;可以揭示数据中的隐藏关系和规律。本文将介绍如何使用Python进行关联规则挖掘&#xff0c;以帮助您发现数据中的有趣模式。 一、引言 1. 简要介绍关联规则学习…

【STM32单片机】----实现LED灯闪烁实战

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

只要0.14元带使能控制500mA输出电流3.3V输出电压SOT23-5封装LDO输入电压可达23V的DS8241-33S5

前言&#xff1a; 本来想推荐的&#xff0c;为慎重&#xff0c;搜了下公司信息&#xff0c;改介绍了。公司相关信息在文尾。 该LDO已在立创销售&#xff0c;希望厂家尽早将该款产品做到基础库或免换料费的器件中。 参考价格&#xff1a;0.14元 使能控制&#xff1a;支持 23V …

PCIe总线-事物层之TLP请求和完成报文格式介绍(六)

1.概述 TLP报文按照类型&#xff0c;可以大致分为4中类型&#xff0c;分别是IO请求报文、存储器请求报文、配置请求报文、完成报文和消息请求报文。IO请求报文可分为IO读请求&#xff08;不携带数据&#xff09;和IO写请求&#xff08;携带数据&#xff09;。存储器请求报文可…

mac 安装Node.js

文章目录 前言一、Node是什么&#xff1f;二、下载三、安装四、验证总结 前言 Node.js是一个开源、跨平台的JavaScript运行时环境&#xff0c;它允许开发者在服务器端运行JavaScript代码。Node.js是基于Chrome V8 JavaScript引擎构建的&#xff0c;它的设计目标是提供一种高效…

番外篇 | YOLOv5-SPD:用最简单的方式完成低分辨率图像和小目标检测升级

前言:Hello大家好,我是小哥谈。论文提出了一个新的CNN构建模块称为SPD-Conv,用来替换每个步长卷转层和每个池化层(从而完全消除它们)。SPD-Conv由一个空间到深度(SPD)层和一个非步长卷积(Conv)层组成。本文详细介绍了如何在YOLOv5中引入SPD-Conv,助力助力低分辨率与小…

pyqt5-结合opencv展示实时视频纯Demo

前言一、代码二、运行结果总结 前言 一、代码 #Author &#xff1a;susocool #Creattime:2024/5/22 #FileName:018.1-显示摄像头 #Description: import sys from PyQt5 import QtWidgets, QtGui, QtCore from PyQt5.QtWidgets import QHBoxLayout import cv2 import osclass V…

k8s集群中pod的容器资源限制和三种探针

一、资源限制 总结&#xff1a; requests表示创建pod时预留的资源&#xff0c;limits表示pod能够使用资源的最大值。requests值可以被超&#xff0c;limits值不能超过&#xff0c;如果是内存使用超过limits会触发oom然后杀掉进程&#xff0c;如果是cpu超过limits会压缩cpu的使用…

Selenium 高频面试题及答案

1、什么是 Selenium&#xff1f;它用于做什么&#xff1f; Selenium 是一个用于自动化测试的开源框架。它提供了多种工具和库&#xff0c;用于模拟用户在不同浏览器和操作系统上的行为&#xff0c;并且可用于测试网页应用程序。 2、Selenium WebDriver 和 Selenium IDE 有何区…

C++学习/复习5--构造函数与初始化/static成员/友元/内部类/匿名对象/编译器的拷贝构造优化

一、本章概要 二、再谈构造函数 1.构造体赋初值与初始化 2.初始化列表与初始化 2.1定义 2.2注意事项与举例 3.explicit关键字与构造函数 3.1隐式类型转换 也叫做自动类型转换 这种转换通常是从存储范围小的类型到存储范围大的类型&#xff0c;或者是从低精度的数值类型到高…

无畏并发: Rust Mutex的基本使用

并发是很多编程语言避不开的一块主要内容&#xff0c;主打一个无畏并发的Rust自然也面临这样的挑战。Rust中的Mutex提供了强大的同步原语&#xff0c;确保共享数据的线程安全&#xff0c;这篇文章中&#xff0c;我们会探讨Mutex的使用&#xff0c;从基础的用法到一些高阶内容。…

基于地理坐标的高阶几何编辑工具算法(8)——整形面

文章目录 工具步骤应用场景算法输入算法输出算法示意图算法原理工具步骤 选中面,点击“整形面”工具,绘制一条和面至少两个交点的线,双击结束。 应用场景 快速修改一个几何面的局部形状。 算法输入 一个待修改的面p,和一条绘制的线l 算法输出 修改后的面 算法示意图…

【JAVA基础之网络编程】UDP和TCP协议以及三次握手和四次挥手的过程

&#x1f525;作者主页&#xff1a;小林同学的学习笔录 &#x1f525;mysql专栏&#xff1a;小林同学的专栏 目录 1. 网络编程 1.1 概述 1.2 网络编程的三要素 1.2.1 IP地址 1.2.2 InetAddress 1.2.3 端口和协议 1.3 UDP协议 1.3.1 UDP发送数据 1.3.2 UDP接收数据 1.4…

Hadoop伪分布式搭建

1 配置SSH免密登录 1.生成密钥 # ssh-keygen -t rsa 注意&#xff1a;需要经过4次回车 查看密钥及公钥 # cd /root/.ssh 拷贝公钥 # cp id_rsa.pub authorized_keys 2 测试本地免密登录 2 下载Hadoop安装包 使用wget命令从华为云上下载Hadoop安装文件 # wget -P /opt https://m…

数组的理论知识

文章目录 数组的理论知识 数组的理论知识 数组是我们在编程时期经常使用到的一种数据结构。 特点&#xff1a; 在连续的内存空间中存储相同数据类型的数据 如图&#xff1a; arr 数组 注意点&#xff1a;数组的修改的效率是比较慢的&#xff0c;O(n)&#xff0c;因为数组只…

6、phpjm混淆解密和php反序列化

题目&#xff1a;青少年雏形系统 1、打开链接也是一个登入面板 2、尝试了sqlmap没头绪 3、尝试御剑&#xff0c;发现一个www.zip 4、下载打开&#xff0c;有一个php文件打开有一段phpjm混淆加密 5、使用手工解混淆 具体解法链接&#xff1a;奇安信攻防社区-phpjm混淆解密浅谈…