学习本意的初衷是想体验一波SSR的渲染模式,利于优化SEO的开发。遇到Prisma后突发奇想的成为前后端一套代码实现的工具。感觉以前做的聊天系统都可以用它来存储聊天数据,用不到indexDB了。
接下来的学习中主要会分两模块去阐述,如果只是想感受下Prisma的开发模式可以跳到第二部分,此次开发以React框架展开实现
一. Next
1. 安装
npx create-next-app
npm i react-icons // 图标库
npm i react-textarea-autosize // 输入框
npm i react-markdown
npm i remark-gfm
npm i --save-dev @types/react-syntax-highlighter
npm i uuid
2. 开发前准备
- 安装后的目录结构上的特点是访问
page
内容的时候通过嵌套在layout
实现,并且在实现功能上的交互点击效果需要在顶部增加'use client'
转换为客户端组件,不然会报错 - 现有的Next会自动提示是否引入了
Tailwindcss
框架(比较流行的UI框架,还是很便于开发的) - 在
app
内创建的文件都可以直接访问路径无需配置,如创建在目录下创建了app/test/page.tsx
,访问地址是http://localhost:3000/test
即可 - 只有根的page文件才可以写body、html标签,其他的页面不需要
- SSR渲染模式是通过服务端渲染执行JS代码,动态生成html内容,把包含网页的html返回给客户端,实现组件方式更为细化的控制渲染模式,优先会先使用服务端组件,但是一些交互性的组件还是会用客户端组件方式呈现
- APP Router 文档的选择
2. 开发
浏览器标签页的名字,在当前page文件中设置
javascript
export const metadata:Metadata = {
title: 'chat测试标签名称',
description: 'chat'
}
GET请求, 创建文件夹: /app/api/test/route.ts
javascript
import { NextRequest, NextResponse } from "next/server";
export async function GET( request: NextRequest ) {
const { url } = request
return NextResponse.json({ url })
}
二. Prisma
安装
npm i prisma --save-dev
npx prisma init // 初始化生成数据库
Vscode的插件安装
配置 schema.prisma
文件
javascript
// 对话模型
model Chat {
id String @id @default(uuid())
title String
updateTime DateTime @updatedAt
Message Message[]
}
// 消息模型
model Message {
id String @id @default(uuid())
content String
role String
createTime DateTime @default(now())
chatId String
chat Chat @relation(fields: [chatId], references: [id])
}
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./chatgpt-app.sqlite" //数据库的路径
}
执行命令生成数据库
npx prisma migrate dev --name init
我们连接的是sqlite,那么我们本级电脑需要下载相应的环境、并在环境变量的path中进行配置,参考网站sqlite3,官网的地址,配置后我们就可以执行
控制台执行方式: sqlite3 prisma/chatgpt-app.sqlite
可视化网站执行方式: npx prisma studio
通过prisma操作数据库
npm i @prisma/client
npx prisma generate // 更新安装的库
Next存在热重载机制,会存在重复数据库,那么我们需要创建相关配置维护全局的prisma的实例, 文件创建/lib/prisma.ts
javascript
import { PrismaClient } from '@prisma/client'
const prismaClientSingleton = () => {
return new PrismaClient()
};
type PrismaClientSingleton = ReturnType<typeof prismaClientSingleton>
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClientSingleton | undefined
}
const prisma = globalForPrisma.prisma || prismaClientSingleton()
export default prisma
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
编写请求的API路由
javascript
// 请求路由 /api/message/update/route.ts
import { NextRequest, NextResponse } from "next/server";
import prisma from "@/lib/prisma";
export async function POST( request: NextRequest ) {
const body = await request.json()
const {id, ...data} = body
if(!data.chatId) {
const chat = await prisma.chat.create({ //創建新的對話放
data: {
title: '新對話'
}
})
data.chatId = chat.id
}
let message = await prisma.message.upsert({
create:data,
update:data,
where:{
id
}
})
return NextResponse.json({code: '00', data: {message}})
}
// 接口请求
const response:any = await fetch("/api/message/update", {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(message)
})
三. React
自定义context全局共享
方法引用
javascript
const {
state: { dispalyNavigation }
} = useAppContext()
setState((v) => {
return {
...v,
dispalyNavigation: !v.dispalyNavigation
}
})
方法定义
javascript
'use client'
import { Dispatch,ReactNode,SetStateAction,createContext,useMemo,useState ,useContext} from "react"
type State = {
dispalyNavigation: boolean
}
type AppCOntextProps = {
state:State
setState: Dispatch<SetStateAction<State>>
}
// 创建Context
const AppContext = createContext<AppCOntextProps>(null!)
// 抛出方法
export function useAppContext() {
return useContext(AppContext)
}
export default function AppCOntextProvider({children}:{children:ReactNode}) {
const [state,setState] = useState<State>({dispalyNavigation:true})
const contextValue = useMemo(()=> {
return {state,setState}
},[state,setState])
return (
// value={{state,setState}}
<AppContext.Provider value={contextValue}>
{children}
</AppContext.Provider>
)
}
配合reducers改造实现
对原有的Congtext改造
javascript
// 原有AppCOntextProps 方法
type AppCOntextProps = {
state:State
dispatch: Dispatch<Action>
}
// 原有const [state,setState] = useState<State>({dispalyNavigation:true})
const [state,dispatch] = useReducer(reducer,initialState)
// 原有改变值是通过setstate去改变的
dispatch({type:ActionType.UPDATE, fieId:'dispalyNavigation',value:!dispalyNavigation})
新建出一个reduce处理的文件夹
javascript
// 初始状态值参数
export type State = {
dispalyNavigation: boolean
}
// 定义枚举类型
export enum ActionType {
UPDATE= 'UPDATE',
}
// 更新数据传递的参数
type UpdateAction = {
type: ActionType.UPDATE
fieId: string
value: any
}
export type Action = UpdateAction
// 初始化状态
export const initialState: State = {
dispalyNavigation: true
}
export function reducer(
state: State = initialState,
action: Action //动作状态
): State {
switch (action.type) {
case ActionType.UPDATE:
return {
...state,
[action.fieId]: action.value
}
default: throw new Error()
}
}