浅尝next13
官网地址
不要看那个所谓的中文网站
创建项目
npx create-next-app@latest
下一步下一步就好了 如图所示
进到项目目录
npm run dev
打开浏览器访问 http://localhost:3000/ 就可以看到页面了
项目结构
- app目录 next13使用app目录,以前的pages目录也可以使用,但是不推荐使用了。默认使用app,我们接下来的例子都是使用app目录,注意他俩的api是不一样的。
- page.tsx 路由文件 可以认为是默认路由
- layout.tsx 布局文件 引入了globals.css 全局样式文件
路由
app目录下面的page.tsx就是路由文件
我们尝试修改一个app/page.tsx文件
export default function Home() {
return <div>666</div>
}
然后刷新页面,就可以看到页面变成了666
样式太难看了,我们把globals.css里面的样式去掉
修改一下启动端口
"scripts": {
"dev": "next dev -p 3012",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
接下来我们要做一个导航栏,点击导航栏可以跳转到其他页面,再首页还得送上接口数据,我们先来做导航栏
app/components/header
import Link from "next/link"
import styles from "./index.module.scss"
export default function Hader() {
return (
<div className={`flex justify-center gap-10 ${styles.header} `}>
<Link className="flex-4" href="/">
首页
</Link>
<Link className="flex-4" href="/about">
关于
</Link>
<Link className="flex-4" href="/list">
列表
</Link>
</div>
)
}
导航组件Link,最后渲染成a标签,href是跳转的地址。 /components/header/index.module.scss
.header {
height: 36px;
> a {
background-color: #909090;
padding: 10px 20px;
display: block;
height: 100%;
}
}
next里面使用样式文件和react里面一样,看个人喜好,我这里 使用了tailwindcss和scss。 需要安装依赖
npm install sass sass-loader -D
添加ts类型文件 typings.d.ts
declare module '*.css';
declare module '*.scss';
declare module '*.sass';
declare module '*.svg';
declare module '*.png';
declare module '*.jpg';
declare module '*.jpeg';
declare module '*.gif';
declare module '*.bmp';
declare module '*.tiff';
declare module 'omit.js';
tsconfig.json 添加
"include": [
...
"**/*.d.ts"
],
注意:如果你把组件放到app目录下并且是page.tsx默认就是路由
layout.tsx
import "./globals.css"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import Hader from "../components/header"
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
<Hader></Hader>
{children}
</body>
</html>
)
}
我们再app目录下分别创建出来对应的页面,记住是 page.tsx
如图,导航没问题了,接下来我们要做的是接口数据。
接口数据
next13 使用fetch来获取数据,返回一个promise,我们先来看看、 假设现在有个接口地址 http://124.71.160.218:3008/tangshi
- 封装一下fetch 具体看个人喜好和具体业务逻辑,我这里是封装了一下fetch
// 封装 HTTP 请求函数
export const BaseUrl = process.env.BASE_URL
export class HTTP {
// 响应拦截器
static responseInterceptors = (res: any) => {
if (res.status === 200) {
return res.data
} else {
return Promise.reject(res.message)
}
}
static get(url: string, data?: any, next?: any, cache?: RequestCache) {
let params = ""
if (data) {
Object.keys(data).forEach((key) => {
params += `${key}=${data[key]}&`
})
params = params.slice(0, -1)
url = `${url}?${params}`
}
return fetch(BaseUrl + url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
next,
cache,
})
.then((res) => res.json())
.then((res) => {
return this.responseInterceptors(res)
})
}
static post(url: string, data?: any, next?: any, cache?: RequestCache) {
console.log(BaseUrl + url)
return fetch(BaseUrl + url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data || {}),
next,
cache,
})
.then((res) => res.json())
.then((res) => {
return this.responseInterceptors(res)
})
}
}
- api接口
import { HTTP } from "./http"
enum apiList {
tangshi = "/tangshi",
}
export async function getTangshiList(params = {}) {
return HTTP.post(apiList.tangshi, params)
}
// 根据id获取详情
export async function getTestDetail(id: string) {
const { data } = await HTTP.get(`${apiList.tangshi}/${id}`)
return data
}
- next.config.js 我们区分了一下配置环境,开发环境和生产环境,接口地址不一样
/** @type {import('next').NextConfig} */
// 获取环境变量
const NODE_ENV = process.env.NODE_ENV
const BASE_URL = {
development: "http://124.71.160.218:3008",
production: "http://124.71.160.218:3008",
}
const nextConfig = {
env: {
BASE_URL: BASE_URL[NODE_ENV],
},
}
module.exports = nextConfig
- app.tsx
import { getTangshiList } from "@/http/api"
import Link from "next/link"
export default async function page() {
const { items }: any = await getTangshiList({})
return (
<div className="text-center mt-2">
<h1 className="text-[20px]">唐诗三百首</h1>
<div className="flex flex-wrap p-2 mt-4">
{items.map((item: any) => {
return (
<div
key={item.id}
className="w-1/6 border text-center hover:bg-gray-200"
>
<Link href={`/tangshi/${item.id}`} className="block p-2">
<h1 className="text-[16px] font-bold">{item.title}</h1>
<h3 className="text-[14px] mt-2">{item.auth}</h3>
</Link>
</div>
)
})}
</div>
</div>
)
}
我们可以看到返回的html,已经是后端渲染好的了。next13默认ssr服务端渲染
控制台我们能看到
这个是我们直接把唐诗三百首全部请求下来了,真实情况应该是分页的,下拉刷新加载更多,接口是支持的。我们来试试,第一次加载10条,那我们把内容也展开一下,加个序号,方便查看。
我们再list页面里实现这个功能.。。。。。
我不想写了。。。。烂尾了。。。。。。。。
大概思路就是你封装个组件,默认加载第一页的。然后。。。。
记得看英文官网就可以了。也挺简单的。
其他
数据缓存
import Http from "@/app/utils/http"
export async function getTangshiList(params = {}) {
const { data } = await Http.post(
"tangshi",
{ ...params },
{
revalidate: 1000, // 请求结果缓存*s
tags: ["refresh"], // 缓存标签
}
)
return data
}
// 根据id获取详情
export async function getTestDetail(id: string) {
const { data } = await Http.get(`tangshi/${id}`)
return data
}
刷新缓存
路径 src\app\api\revalidate
import { NextRequest, NextResponse } from "next/server"
import { revalidateTag } from "next/cache"
// 请求路径为 http://localhost:4002/api/revalidate?tag=refresh&_t=23
// tag=refresh 时,强制刷新
export async function GET(request: NextRequest) {
const tag = request.nextUrl.searchParams.get("tag")!
console.log("11111111", request.nextUrl.searchParams.get("tag"))
revalidateTag(tag)
return NextResponse.json({
refresh: tag === "refresh",
now: Date.now(),
message: tag === "refresh" ? "刷新" : "没有刷新",
})
}
获取参数
[id]/page.tsx 中口号【】就表示参数
import { getTestDetail } from "@/api"
import React from "react"
export default async function page({ params }: { params: { id: string } }) {
const { id } = params
const data: any = await getTestDetail(id)
console.log("data", data)
return (
<div className="text-center mt-6">
<h1 className="text-[20px] font-bold">{data.title}</h1>
<h3 className="text-[16px] mt-2">{data.auth}</h3>
<div className="mt-4 ">
{data.content.map((item: string, index: number) => {
return (
<p key={index} className="text-[20px] font-bold">
{item}
</p>
)
})}
</div>
{/* desc 内容回显到html里面 */}
<div
className="m-auto text-center mt-12 w-6/12 "
dangerouslySetInnerHTML={{ __html: data.desc }}
></div>
<div>
<button className="mt-4 bg-blue-500 text-white p-2 pl-6 pr-6 rounded ">
<a href="/">首页</a>
</button>
</div>
</div>
)
}
打包
默认的是ssr服务器渲染
打包成静态
我的特殊,我打包成静态资源了。就不能使用服务器组件这些了。
/** @type {import('next').NextConfig} */
// 获取环境变量
const NODE_ENV = process.env.NODE_ENV;
const nextConfig = {
// output: "export", // 导出静态的html。但是不能使用服务端渲染
// Optional: 修改路由,路径后面添加/ `/about` -> `/about/`
// trailingSlash: true,
// Optional: Change the output directory `out` -> `dist`
distDir: 'dist',
// Image 不支持 export 导出。。。
images: {
// Optional: 修改图片域名
// domains: ["localhost"], // 本地开发
domains: ['localhost'],
},
};
// 生产环境 配置导出静态html
if (NODE_ENV === 'production') {
// 不能和代理同时使用Error: Specified "rewrites" cannot be used with "output: export".
nextConfig.output = 'export';
} else {
// 本地开发环境 配置代理
nextConfig.rewrites = async () => {
return [
{
source: '/api/:path*',
destination: 'http://192.168.10.238:8020/:path*',
},
];
};
}
module.exports = nextConfig;
dist目录里面每个路由都生成了html文件。
所以是多页面。记得修改一下nginx
location / {
# 将以.html结尾的请求添加Cache-Control头信息
if ($uri ~* \.(html)$) {
add_header Cache-Control "private, no-store, no-cache, must-revalidate, proxy-revalidate";
}
# 默认情况下,根据路径转发,找不到加载index.html
try_files $uri /$uri.html /index.html;
}
根据路径加载不同的html页面