NextJS 学习笔记

NextJS 学习笔记

NextJS

什么是 NextJS

我的理解 NextJS 是一个基于 React 的全栈框架

本文章只关于 NextJS的功能 不涉及React和前端

创建 NextJS APP

yarn create next-app

NextJS APP 的脚本

开发模式
"dev": "next dev",

构建
"build": "next build",

生产环境
"start": "next start",

导出
"export": "next export",

build、export 的区别

  • next build

    构建后,可以使用 next start 开启服务器监听,可以处理动态理由和服务器逻辑

  • next export

    导出后,生成一个 out 目录的纯静态页面,可以使用托管或者 Nginx 部署

启动开发模式

npm run dev

打开 http://localhost:3000/

静态路由

通俗来说,你的 pages 目录中有什么文件,你的服务器就会有什么路由

例如我定义了 about.tsx 再访问 http://localhost:3000/about 就能看到

多级路由(嵌套路由)同理

例如我定义了 pages/me/about.tsx 那么就是 http://localhost:3000/me/about

几个固定的名称:

  • index.tsx 目录路由假如你再 pages 下新建 index.tsx 对应 http://localhost:3000/
  • 404.tsx 页面未找到时
  • /pages/_app.tsx 入口文件

构建后,会在终端显示树形图

Route (pages)                             Size     First Load JS
┌ ○ /                                     280 B          78.5 kB
├   /_app                                 0 B            78.2 kB
├ ○ /404                                  180 B          78.4 kB
├ ○ /about                                268 B          78.5 kB
└ ƒ /api/hello                            0 B            78.2 kB
  • ○ 纯静态页面路由
  • ƒ 服务器端路由

例如要替换掉默认的 404 只需要自己在 pages 新建一个 404.tsx 就行了

动态路由

当文件名为 [*].tsx 时,此页面为动态路由

例如 我创建一个 pages/user/[UserID].tsx 文件

用户访问 http://localhost:3000/user/123.tsx 就会路由到此文件

当然,动态路由是可以在 tsx 中接收参数的,只需要使用 useRouter Hook

import { useRouter } from "next/router"

export default function Page() {
    const router = useRouter()
    const { UserID } = router.query

    return <>
    欢迎用户 {UserID}
    </>
}

以此类推

嵌套动态路由只需要将目录命名为 [*] 就能实现

那么 如果有两个动态路由在同一层级下会怎么样?

答案是 他会报错,这样是不允许的,记住,同一路径下只能由一个动态路径。

不过 动态路径和静态路径是允许在同一目录下的,NextJS 会优先匹配静态路径,其次是动态路径,最后是 404 页面。

多级路由的写法 [...args]

const args: string | string[] | undefined = router.query.args

服务端渲染

服务端渲染是什么?

我的理解是:当页面需要加载额外数据时,服务器请求并渲染

对比客户端渲染的特点:可以访问内网资源,利于搜索引擎爬虫收录
NextJS 入门-01

服务端构建实现案例

这是一个最简单的服务端构建页面

export default function Page(props: { data: any }) {

    // 返回的 json 结构 这里使用 https://pokemon.fantasticmao.cn/pokemon/list 作为测试
    type Pokemon = {
        index: number  // 序号
        nameZh: string  // 宝可梦名称
        type1: number  // 属性1
        type2: number  // 属性2
    }

    const data: Pokemon[] = props.data

	// 客户端渲染部分
    return <ul>{data.map((item: Pokemon) => {
        return (<>
            <li>名称:{item.nameZh}</li>
            <li>主属性:{item.type1}</li>
            {item.type2 && <li>副属性:{item.type2}</li>}
            <hr />
        </>
        )
    })}</ul>
}

export async function getStaticProps() {
	// 服务端渲染
	// 获取数据
    const response = await fetch("https://pokemon.fantasticmao.cn/pokemon/list")
    const response_json = await response.json()

    return {
        props: {
            data: response_json.data
        }
    }
}

接下来解释这个案例:

  • getStaticProps 实现资源获取,通过 props 交给 Page 渲染

测试接口:https://pokemon.fantasticmao.cn/pokemon/list

image

渲染结果:

image

image

浏览器抓包可以看到,并没有请求 https://pokemon.fantasticmao.cn/pokemon/list 而是服务器请求后返回了结果给前端。

通过用户参数进行服务端构建

案例:

type Pokemon = {
    index: number  // 序号
    nameZh: string  // 宝可梦名称
    type1: number  // 属性1
    type2: number  // 属性2
}

export default function Page(props: { data: Pokemon }) {
    const data: Pokemon = props.data

    return <ul>
        <li key={data.index} >
            <div>序号:{data.index}</div>
            <div>名称:{data.nameZh}</div>
            <div>主属性:{data.type1}</div>
            {data.type2 && <div>副属性:{data.type2}</div>}
            <hr />
        </li>
    </ul>
}

// 新增 getStaticPaths 函数
export async function getStaticPaths() {
    const response = await fetch("https://pokemon.fantasticmao.cn/pokemon/list")
    const response_json = await response.json()

    return {
        paths: response_json.data.map((item: Pokemon) => ({params: {PokemonID: item.index.toString()}})),
        fallback: false
    }
}

// 改变 getStaticProps 函数
export async function getStaticProps(context: any) {
    const response = await fetch("https://pokemon.fantasticmao.cn/pokemon/list")
    const response_json = await response.json()
    //                                                                 使用 context.params.PokemonID获取路径参数
    const item = response_json.data.find((item: Pokemon) => item.index === parseInt(context.params.PokemonID))
    return {
        props: {
            data: item
        }
    }
}

用户访问效果:

image

服务端会先请求API,确定用户可以访问那些页面

然后通过 getStaticProps通过用户的URL参数去给出指定数据

最后交给 Page进行客户端渲染

有用户参数的服务器渲染的静态构建

此时每个宝可梦的ID将生成一个静态页面

如果宝可梦的ID为404那么会占用掉原本的 404.tsx 所以 要使用嵌套动态路由来避免

例如 /Pokemon/[PokemonID].tsx

或者为URL参数加上前缀

return {
    paths: response_json.data.map((item: Pokemon) => ({params: {PokemonID: "p_" + item.index.toString()}})),
    fallback: false
}

Link / Router 进行路由跳转

Link

比起直接使用 a 标签,NextJS 中更加推荐使用 Link

import Link from "next/link"

Link标签可以进行对要跳转页面的预加载, 适合做固定的导航

<Link href={"/p_" + (data.index + 1)}>下一页</Link>

Router

对比 Link router适合做程序化导航,也提供了更加精细的控制

例如实现页面的返回

<button onClick={() => router.back()}>返回</button>

也可以用 router.push 进行页面跳转 但不会预加载

<button onClick={() => router.push("/")}>首页</button>

手动预加载某个页面

router.prefetch('/about');

替换当前url,例如我在 /aaa/1 想要跳到 /aaa/2 就可以使用 router.replace

router.replace('2');
// 这对翻页应用非常有用

按需构建静态页面

前面说到,每个宝可梦ID,都会生成一个静态页面,数据一多就会造成生成非常慢,这时候就要用到按需构建

export async function getStaticPaths() {
    // const response = await fetch("https://pokemon.fantasticmao.cn/pokemon/list")
    // const response_json = await response.json()

    return {
        paths: [],
        // paths: response_json.data.map((item: Pokemon) => ({ params: { info: "p_" + item.index.toString() } })),
        fallback: "blocking" // <- 改
    }
}

export async function getStaticProps(context: any) {
    const response = await fetch("https://pokemon.fantasticmao.cn/pokemon/list")
    const response_json = await response.json()

	// 此时 服务器并不知道有哪些页面 因为是按需构建 就需要去实时获取判断是否有这个页面  
    let item
    try {
        item = response_json.data.find((item: Pokemon) => item.index === parseInt(context.params.info.split("p_")[1]))
    } catch (error) {
		// 用户参数错误时 返回404
        return {
            notFound: true
        }
    }

	// 没有找到页面是 返回404
    if (!item) {
        return {
            notFound: true
        }
    }

    return {
        props: {
            data: item
        }
    }
}

fallback 为 true

当 fallback 为 true时,也是按需构建,区别就是可以使用 router.isFallback 获取构建状态

export default function Page(props: { data: Pokemon }) {
...
    const router = useRouter()

    if (router.isFallback) {
        return <div>Loading...</div>
    }
...
}

这样,构建的时候前端就会显示 Lodding... 让用户直观的看到过程

增量更新内容

可以让构建的内容多少时间内不再重复构建,为页面设置有效期

如何实现?

getStaticProps 的返回参数加上 revalidate:6000其中6000就是有效期,单位是秒

return {
	props: {
    	data: item
    },
    revalidate: 6000
}

页面信息设置

通过 NextJS 的 main 标签在 Page中设置

<main>
      <head>
          <title>标题</title>
      </head>
</main>

把项目上传到 Vercel 托管

  • https://vercel.com/ 注册一个账号
  • 为你的项目创建一个 Github 仓库

打开 https://vercel.com/new/ 新建项目

LICENSED UNDER CC BY-NC-SA 4.0
Comment