本章节,我们基于前面学习的知识,做一个简单的TodoList示例。
只是展示前边说过的几个库的用法,所以一切从简,不会考虑太多实际开发中的情况
比如删除是真删除,尽量只用一个表演示下各种用法
项目的创建以及依赖的安装在前面的章节已经说过了,这里就不重复了。
完整代码存放于GitHub
Schema 1 2 3 4 5 6 7 8 9 model Todo { id Int @id @default(autoincrement()) content String finished Boolean @default(false) createdAt DateTime @default(now()) @map("created_at") @@map("todos") }
模型更改后需要同步到数据库:
1 npx prisma migrate dev --name init
静态页面 我们添加shadcn ui的组件:
先封装两个组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 "use client" ;import { ChangeEvent, useState } from "react" ;import { Button } from "./ui/button" ;import { Input } from "./ui/input" ;export default function InputSection ( ) { const [inputVal, setInputVal] = useState("" ); const handleInputValChange = (evt: ChangeEvent<{value: string }> ) => { setInputVal(evt.target.value) }; return ( <div className ="flex items-center space-x-4" > <Input placeholder ="Please enter here..." value ={inputVal} onChange ={handleInputValChange} /> <Button > Add</Button > </div > ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 import { Button } from "./ui/button" ;import { Checkbox } from "./ui/checkbox" ;export default function TodoItem ( ) { return ( <div className ="flex items-center space-x-2" > <Checkbox /> <span > 内容</span > <Button size ="sm" > Delete</Button > </div > ) }
在页面中引入:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import InputSection from "@/components/input-section" ;import TodoItem from "@/components/todo-item" ;export default function Home ( ) { return ( <main className ="pt-8" > <div className ="w-[800px] mx-auto" > {/* 录入部分 */} <InputSection /> {/* 未完成的事项 */} <div className ="my-6" > <TodoItem /> </div > {/* 已完成的事项 */} <div > <TodoItem /> </div > </div > </main > ); }
API 我们创建的api目录结构为:
1 2 3 4 5 - app - api - route.ts - [id] - route.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import prisma from "@/db/prisma" export async function GET ( ) { const todos = await prisma.todo.findMany() return Response.json(todos) } export async function POST (request: Request ) { const body = await request.json() const todo = await prisma?.todo.create({ data : body }) return Response.json(todo) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import prisma from "@/db/prisma" ;export async function PATCH ( request: Request, { params }: { params: { id: string } } ) { const finished = await request.json(); const id = Number (params.id); const res = await prisma.todo.update({ where : { id, }, data : { finished, }, }); return Response.json(res); } export async function DELETE (_: any , { params }: { params: { id: string } } ) { const res = await prisma.todo.delete({ where : { id : Number (params.id) } }) return Response.json(res) }
对接接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 "use client" ;import InputSection from "@/components/input-section" ;import TodoItem from "@/components/todo-item" ;import { TodoItemType } from "@/types/todo" ;import { useQuery } from "@tanstack/react-query" ;export default function Home ( ) { const getAllTodos = async () => { const r = await fetch("/api/todo" ); const res = await r.json(); return res; }; const query = useQuery({ queryKey : ["todos" ], queryFn : getAllTodos, }); return ( <main className ="pt-8" > <div className ="w-[800px] mx-auto" > {/* 录入部分 */} <InputSection /> {/* 未完成的事项 */} <h2 className ="mt-6" > Todo:</h2 > <div className ="mb-6" > {query.data ?.filter((todo: TodoItemType) => !todo.finished) .map((todo: TodoItemType) => { return <TodoItem key ={todo.id} todo ={todo} /> ; })} </div > {/* 已完成的事项 */} <h2 > Finished:</h2 > <div > {query.data ?.filter((todo: TodoItemType) => todo.finished) .map((todo: TodoItemType) => { return <TodoItem key ={todo.id} todo ={todo} /> ; })} </div > </div > </main > ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 "use client" ;import { ChangeEvent, useState } from "react" ;import { Button } from "./ui/button" ;import { Input } from "./ui/input" ;import { useMutation, useQueryClient } from "@tanstack/react-query" ;export default function InputSection ( ) { const queryClient = useQueryClient(); const [inputVal, setInputVal] = useState("" ); const handleInputValChange = (evt: ChangeEvent<{ value: string }> ) => { setInputVal(evt.target.value); }; const handleAdd = async (data: { content : string }) => { const res = await fetch("/api/todo" , { method : 'POST' , body : JSON .stringify(data) }); const r = await res.json() return r }; const mutation = useMutation({ mutationFn : handleAdd, onSuccess : () => { setInputVal('' ) queryClient.invalidateQueries({ queryKey : ["todos" ] }); }, }); return ( <div className ="flex items-center space-x-4" > <Input placeholder ="Please enter here..." value ={inputVal} onChange ={handleInputValChange} /> <Button onClick ={() => { mutation.mutate({ content: inputVal, }); }} > Add </Button > </div > ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 'use client' import { TodoItemType } from "@/types/todo" ;import { Button } from "./ui/button" ;import { Checkbox } from "./ui/checkbox" ;import { useMutation, useQueryClient } from "@tanstack/react-query" ;export default function TodoItem ({todo}: {todo: TodoItemType} ) { const queryClient = useQueryClient() const patchMutation = useMutation({ mutationFn : async (checked: boolean ) => { const r = await fetch(`/api/todo/${todo.id} ` , { method : 'PATCH' , body : JSON .stringify(checked) }) const res = await r.json() return res }, onSuccess : () => { queryClient.invalidateQueries({queryKey : ['todos' ]}) } }) const deleteMutation = useMutation({ mutationFn : async () => { const r = await fetch(`/api/todo/${todo.id} ` , { method : 'DELETE' }) return await r.json() }, onSuccess : () => { queryClient.invalidateQueries({ queryKey : ['todos' ] }) } }) return ( <div className ="flex items-center space-x-2 mb-3 last:mb-0" > <Checkbox checked ={todo.finished} onCheckedChange ={(checked: boolean ) => { patchMutation.mutate(checked) }} /> <span className ={ todo.finished ? 'line-through ' : '' }> {todo.content}</span > <Button size ="sm" onClick ={() => { deleteMutation.mutate() }}>Delete</Button > </div > ) }
至此,我们就完成了一个很简单的具备CRUD功能的TODOList