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
静态路由
通俗来说,你的 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
服务端渲染
服务端渲染是什么?
我的理解是:当页面需要加载额外数据时,服务器请求并渲染
对比客户端渲染的特点:可以访问内网资源,利于搜索引擎爬虫收录
服务端构建实现案例
这是一个最简单的服务端构建页面
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
渲染结果:
浏览器抓包可以看到,并没有请求 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
}
}
}
用户访问效果:
服务端会先请求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/ 新建项目
评论