环境
- Next.js 14
- React 18
- Mongodb
前言
花了两周时间学习了Next.js, 自己做了个demo,尝试了下服务器端渲染,客户端渲染,给人的感觉就是又像回到了asp.net MVC时代, 需要在页面初次加载时显示的数据可以使用ViewModel来解决,需要在页面上有交互、异步刷新的业务可以使用ajax来解决。
最主要的是整理了使用Next.js 项目结构,一些文件、目录应该怎么放。
项目结构(目录结构)
- 需要区分服务器端渲染和客户端渲染的页面,Next.js 14版本推荐服务器端渲染页面是放在app目录下,按照目录约定方式配置路由。 比如这里app/addTopic, app/editTopic就对应两个路由地址
http://localhost:3000/addTopic
http://localhost:300/editTopic.
如果需要带路由参数,是把文件夹名称使用中括号包装起来
- 需要通过客户端渲染的页面建议创建Views or Pages目录,主要是和服务器渲染组件区分开,并且使用'use client' 指令描述
- API Endpoint - Next.js 可以创建服务器端接口, 就像asp.net MVC里创建Controller/Action 可以在Razor 视图里异步调用。 默认放在app/api目录下,如上图,根据约定就会暴露出如下几个api endpoint
http://localhost:3000/api/topics
http://localhost:3000/api/topics/{id}
然后具体的http协议可以在代码里指定,可以是POST OR GET OR PUT. 后面会贴上参考代码。 这一点我个人发现如果是复杂一些的路由,可能会在api目录先出现嵌套很多的目录结构,比如说URL上包含很多查询参数,按照Restful URL设计的话, 此时项目结构可能就有点凌乱,体验不是很好。
API Endpoint 代码片段
app/topics/route.ts
/**
* POST
* http://localhost:3000/api/topics
* **/
export async function POST(request: any) {
const { title, description } = await request.json();
await connectMongoDb();
await Topic.create({ title, description });
return NextResponse.json({ message: "Topic Created" }, { status: 201 });
}
/**
* GET
* http://localhost:3000/api/topics
* **/
export async function GET() {
await connectMongoDb();
const topics = await Topic.find();
return NextResponse.json(topics);
}
/**
* DELETE
* http://localhost:3000/api/topics?id=123
* **/
export async function DELETE(request: any) {
const id = request.nextUrl.searchParams.get("id");
await connectMongoDb();
await Topic.findByIdAndDelete(id);
return NextResponse.json({ message: "Topic Deleted" }, { status: 200 });
}
app/topics/[id]/route.ts
import connectMongoDb from "@/libs/mongodb";
import Topic from "@/models/topic";
import { NextResponse } from "next/server";
/**
* http get
* http://localhost:3000/api/topics/[id]
*/
export async function GET(request: any, { params }: any) {
const { id } = params;
await connectMongoDb();
const topic = await Topic.findOne({ _id: id });
return NextResponse.json({ topic }, { status: 200 });
}
/**
* http put
* http://localhost:3000/api/topics/[id]
*/
export async function PUT(request: any, { params }: any) {
const { id } = params;
//从request body 中解析参数newTitle, newDescription 给title, description 赋值
const { newTitle: title, newDescription: description } = await request.json();
await connectMongoDb();
await Topic.findByIdAndUpdate(id, { title, description });
return NextResponse.json({ message: "Topic Updated" }, { status: 200 });
}
使用postman测试接口, 在mongodb里查看数据
[POST]插入一条数据
[GET]获取所有数据
[PUT]修改数据
列表页面
const getTopics = async () => {
const res = await fetch('http://localhost:3000/api/topics', {
method: 'GET',
cache: 'no-cache'//不使用缓存
});
return res.json();
}
export default async function TopicList() {
const topics = await getTopics();
return (
<>
{topics.map((t: Topic) => (
<div
key={t._id}
className="p-4 border border-slate-300 my-3 flex justify-between gap-5 items-start">
<div>
<h2 className="font-bold text-2xl">{t.title}</h2>
<div>{t.description}</div>
</div>
<div className="flex gap-2">
<RemoveBtn id={t._id} />
<Link href={`/editTopic/${t._id}`}>
<HiPencilAlt size={24} />
</Link>
</div>
</div>
))}
</>
)
}
列表页面使用服务器端渲染, 页面初始化时就去调用接口加载数据,这里会发现【删除】按钮就单独封装出来,需要客户端交互就需要使用客户端组件,也就是普通的React组件。
删除组件
删除组件顶部需要使用'use client'指令
'use client'
import { HiOutlineTrash } from "react-icons/hi";
import { useRouter } from "next/navigation";
export default function RemoveBtn({ id }: any) {
const router = useRouter();
const removeTopic = async () => {
const confirmed = confirm("Are you sure?");
if (confirmed) {
const res = await fetch(`http://localhost:3000/api/topics?id=${id}`, {
method: "DELETE"
})
if (res.ok) {
router.refresh();
}
}
}
return (
<button onClick={removeTopic} className="text-red-400">
<HiOutlineTrash size={24} />
</button>
)
}
连接Mongodb
connect
import mongoose from "mongoose";
const connectMongoDb = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI!);
console.log("Connected to MongoDB");
} catch (error) {
console.log(error);
}
}
export default connectMongoDb;
Schema
import mongoose, { Schema } from "mongoose";
const topicSchema = new Schema(
{
title: { type: String, required: true },
description: { type: String, required: true }
},
{
timestamps: true
}
)
const Topic = mongoose.models.Topic || mongoose.model('Topic', topicSchema);
export default Topic;
参考
https://gitee.com/garfieldzf/next-topiclist-app
标签:const,Mongodb,topics,await,js,Topic,CURD,http,id From: https://www.cnblogs.com/sword-successful/p/18201623