משימוש בפעולות CRUD הוא בסיס לכל אפליקציה, לכן חשוב להפוך למומחה בהן בזמן לימוד טכנולוגיות חדשות.
במדריך הוראות הזה, תלמדו איך לבנות אפליקציה CRUD בעזרת React ו-Convex. אנחנו נעבור על הפעולות האלה על ידי בניית פרוייקט שנקרא Book Collections. בפרוייקט הזה, משתמשים יוכלות להוסיף ספרים ולעדכון את מצבם אחרי שיגשגו ספר.
תוכן המדריך
מהו Convex?
Convex היא פלטפורמת Baas המקדםת פיתוח חשבון רקע. Convex מוצעת בעזרת בסיס נתונים בזמן אמת, ואתה לא צריך לדאוג על כתיבת הלוגיקה צד שרבים בגלל שהיא מעניקה שיטות לשאילת ומשינוי הבסיס הנתונים.
דרישות קדם
כדי לעבוד עם המדריך הזה, עליך לדעת את הבסיס של React. אני אשתמש בTypeScript בפרוייקט הזה, אך זה אופציה, אז אתה גם יכול לעקוב עם JavaScript.
איך להגדיר את הפרוייקט שלך
יציר תיקייה נפרדת לפרוייקט ותנהג אחר שמה – אני אקרא לשלי Books. אנחנו ניצור את Convex וReact בתיקייה הזו.
ניתן ליצור אפליקציית React בעזרת הפקודה הבאה:
npm create vite@latest my-app -- --template react-ts
אם אתה רוצה לעבוד עם JavaScript, אז תסיר את ts
בסוף. זה הולך להיות:
npm create vite@latest my-app -- --template react
איך להגדיר את Convex
אנחנו צריכים להתקין Convex באותה תיקייה. אתה יכול לעשות את זה בעזרת הפקודה הבאה:
npm install convex
בהמשך, הוריד npx convex dev
. אם זה הפעם הראשונה שאתה עושה את זה, היא צריכה לבקש אמת כרטיס. אחרת, היא צריכה לבקש את שמו הפרוייקט.
תוכלו לבקר בלוח הבקרה Convex כדי לראות את הפרויקט שיצרתם.
עכשיו, כיוון שיש לנו את Convex ואפליקציית React המוגדרות, אנחנו צריכים לחבר את Convex backend לאפליקציית React.
בקובץ src/main.tsx, עטוף את הרכיב App
עם ConvexReactClient
:
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
import { ConvexProvider, ConvexReactClient } from "convex/react";
import "./index.css"
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
createRoot(document.getElementById("root")!).render(
<ConvexProvider client={convex}>
<App />
</ConvexProvider>
);
כאשר הגדרתם Convex, נוצר קובץ .env.local
. אתם יכולים לראות את כתובת ה-URL של השרת בקובץ זה.
בשורה שלמטה, התקנו את לקוח React Convex עם ה-URL.
const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
איך ליצור את הסכמה
בתיקיית הפרויקט הראשית, אתם צריכים לראות את תיקיית convex. נטפל לשאילתות המסד הנתונים והמוטציות כאן.
צרו קובץ schema.ts בתיקיית convex:
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
books: defineTable({
title: v.string(),
author: v.string(),
isCompleted: v.boolean(),
}),
});
אתם יכולים להגדיר סכמה למסמך שלכם עם defineSchema
וליצור טבלה עם defineTable
. Convex מספק את הפונקציות האלה להגדרת סכמה ויצירת טבלה.
v
הוא מאמת הסוג, הוא משמש לספק סוגים לכל נתונים שאנו מוסיפים לטבלה.
לפרויקט זה, כיוון שזוהי אפליקציית אוסף ספרים, המבנה יכלול title
, author
, ו-isCompleted
. אתם יכולים להוסיף שדות נוספים.
עכשיו, כיוון שהגדרתם את הסכמה, הגדרנו את הממשק המשתמש הבסיסי ב-React.
איך ליצור את הממשק המשתמש
בתוך התוך src יצירו תיקייה בשם component וקובץ Home.tsx. פה, תוכלו להגדיר את הUI.
import { useState } from "react";
import "../styles/home.css";
const Home = () => {
const [title, setTitle] = useState("");
const [author, setAuthor] = useState("");
return (
<div className="main-container">
<h1>Book Collections</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
name="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="book title"
/>
<br />
<input
type="text"
name="author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
placeholder="book author"
/>
<br />
<input type="submit" />
</form>
{books ? <Books books={books} /> : "Loading..."}
</div>
);
};
export default Home;
תוכלו ליצור את הרכיב שלכם כפי שתרצו. הוספתי שתי שדות קלט לשם title
ו author
וכפתור submit
. זוהי המבנה הבסיסי. עכשיו נוכל ליצור שיטות CRUD בחלק הרקע.
איך ליצור פעולות CRUD
בתוך התיקייה convex, תוכלו ליצור קובץ נפרד queries.ts עבור הפעולות CRUD.
יצירת פעולה
ב convex/queries.ts:
תוכלו להגדיר פעולה createBooks
. תוכלו להשתמש בפעולה mutation
מ Convex כדי ליצור, לעדכן ולמחוק מידע. הקריאה למידע תהיה תחת query
.
הפעולה mutation
מצפה לאתרגומים אלה:
-
agrs
: המידע שאנחנו צריכים לשמור בבסיס הנתונים. -
handler
: מטפל בהגיון לשמור על התאריך בבסיס הנתונים. הhandler
הוא פעולה אסירת תהליך, ויש לה שני אגונים:ctx
וargs
. כאן,ctx
הוא אג 'ונד ההצגה שאנחנו נשתמש בו כדי לטפל בהערכת האופרציות בבסיס הנתונים.
אתה תהליך בשימוש בשיטה insert
להכניס נתונים חדשים. המשתנה הראשון בשיטה insert
הוא שם הטבלה, והשני הוא הנתונים שצריך להיכנס.
לבסוף, אתה יכול לשחזר את הנתונים מהבסיס המידעי.
הנה הקוד:
import { mutation} from "./_generated/server";
import { v } from "convex/values";
export const createBooks = mutation({
args: { title: v.string(), author: v.string() },
handler: async (ctx, args) => {
const newBookId = await ctx.db.insert("books", {
title: args.title,
author: args.author,
isCompleted: false,
});
return newBookId;
},
});
פונקציית קריאה
ב convex/queries.ts:
import { query } from "./_generated/server";
import { v } from "convex/values";
//read
export const getBooks = query({
args: {},
handler: async (ctx) => {
return await ctx.db.query("books").collect();
},
});
בפעולה הקריאה הזו, השתמשנו בפונקציית query
הבניינית מ Convex. כאן, args
יהיה ריק מפני שאנחנו לא מקבלים נתונים מהמשתמש. באותו אופן, הפונקציית handler
היא אסירת המחדל ומשתמשת באובייקט ctx
כדי לשאול את הבסיס המידעי ולשחזר את הנתונים.
פונקציית עדכון
ב convex/queries.ts:
יוצר פונקציית updateStatus
. אנחנו רק נעדכן את המצב isCompleted
.
פה, צריך לקבל את המזהה של המסמך ואת המצב מהמשתמש. ב args
, אנחנו נוסיף id
ו isCompleted
, שיגיעו מהמשתמש.
בפונקציית handler
, נשתמש בשיטה patch
כדי לעדכן את הנתונים. שיטת patch
מצפה לשתי תרגומים: הראשון הוא המזהה של המסמך, והשני הוא הנתונים העדכונים.
import { mutation } from "./_generated/server";
import { v } from "convex/values";
//update
export const updateStatus = mutation({
args: { id: v.id("books"), isCompleted: v.boolean() },
handler: async (ctx, args) => {
const { id } = args;
await ctx.db.patch(id, { isCompleted: args.isCompleted });
return "updated"
},
});
פונקציית מחיקה
ב convex/queries.ts:
מחק ספרים
import { mutation } from "./_generated/server";
import { v } from "convex/values";
מחיקה
export const deleteBooks = mutation({
args: { id: v.id("books") },
handler: async (ctx, args) => {
await ctx.db.delete(args.id);
return "deleted";
},
});
src/component/Home.tsx
import { useQuery, useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Books } from "./Books";
import { useState } from "react";
import "../styles/home.css";
const Home = () => {
const [title, setTitle] = useState("");
const [author, setAuthor] = useState("");
const books = useQuery(api.queries.getBooks);
const createBooks = useMutation(api.queries.createBooks);
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
createBooks({ title, author })
.then(() => {
console.log("created");
setTitle("");
setAuthor("");
})
.catch((err) => console.log(err));
};
return (
<div className="main-container">
<h1>Book Collections</h1>
<form onSubmit={handleSubmit}>
<input
type="text"
name="title"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="book title"
/>
<br />
<input
type="text"
name="author"
value={author}
onChange={(e) => setAuthor(e.target.value)}
placeholder="book author"
/>
<br />
<input type="submit" />
</form>
{books ? <Books books={books} /> : "Loading..."}
</div>
);
};
export default Home;
const books = useQuery(api.queries.getBooks);
const createBooks = useMutation(api.queries.createBooks);
בתוך Home.tsx
const handleSubmit = (e: React.FormEvent<HTMLFormElement>): void => {
e.preventDefault();
createBooks({ title, author })
.then(() => {
console.log("created");
setTitle("");
setAuthor("");
})
.catch((err) => console.log(err));
};
כשתלחצו על שליחה, זה מוצע את הפעילות handleSubmit
.
אנחנו השתמשנו ב createBooks
כדי להעביר את title
וauthor
מהמצב. הפעילות הסוף היא אסינכרונית, אז אנחנו יכולים להשתמש ב handleSubmit
כאסינכרוני או להשתמש ב .then
. אני השתמשתי בשיטת .then
כדי להתמודד עם המידע האסינכרוני.
ניתן ליצור רכב נפרד כדי להציג את המידע שנאסף מהמאגר. המידע שחוזר הוא ב Home.tsx, אז אנחנו נעביר את המידע לרכב Books.tsx באופן משתמש.
ב Books.tsx:
import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";
export const Books = ({ books }: { books: book[] }) => {
const [update, setUpdate] = useState(false);
const [id, setId] = useState("");
const deleteBooks = useMutation(api.queries.deleteBooks);
const updateStatus = useMutation(api.queries.updateStatus);
const handleClick = (id: string) => {
setId(id);
setUpdate(!update);
};
const handleDelete = (id: string) => {
deleteBooks({ id: id as Id<"books"> })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
};
const handleUpdate = (e: React.FormEvent<HTMLFormElement>, id: string) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
const isCompleted: boolean =
(formdata.get("completed") as string) === "true";
updateStatus({ id: id as Id<"books">, isCompleted })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
setUpdate(false);
};
return (
<div>
{books.map((data: book, index: number) => {
return (
<div
key={data._id}
className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
>
<h3>Book no: {index + 1}</h3>
<p>Book title: {data.title}</p>
<p>Book Author: {data.author}</p>
<p>
Completed Status:{" "}
{data.isCompleted ? "Completed" : "Not Completed"}
</p>
<button onClick={() => handleClick(data._id)}>Update</button>
{id === data._id && update && (
<>
<form onSubmit={(e) => handleUpdate(e, data._id)}>
<select name="completed">
<option value="true">Completed</option>
<option value="false">Not Completed</option>
</select>
<input type="submit" />
</form>
</>
)}
<button onClick={() => handleDelete(data._id)}>delete</button>
</div>
);
})}
</div>
);
};
ברכב Books.jsx, ניתן להציג מידע מהמאגר ולהתמודד עם הפעילויות לעדכון ומחיקת רשימות.
בואו נעבור דרך כל אחד מהתכונות האלה בצעדים.
איך להציג את המידע
ניתן לקבל את המידע שמעבר בתור תכונה ברכב Home.tsx
. אם אתם משתמשים בTypeScript, הגדרתי סוג עבור האובייקט שחוזר מהשאלה. אתם יכולים להתעלמ מזה אם אתם משתמשים בJavaScript.
יצירות `books.types.ts`:
export type book = {
_id: string,
title: string,
author: string,
isCompleted: boolean
}
ניתן להשתמש בפונקציית map
כדי להציג את המידע.
import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";
export const Books = ({ books }: { books: book[] }) => {
const [update, setUpdate] = useState(false);
return (
<div>
{books.map((data: book, index: number) => {
return (
<div
key={data._id}
className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
>
<h3>Book no: {index + 1}</h3>
<p>Book title: {data.title}</p>
<p>Book Author: {data.author}</p>
<p>
Completed Status:{" "}
{data.isCompleted ? "Completed" : "Not Completed"}
</p>
<button onClick={() => handleClick(data._id)}>Update</button>
{id === data._id && update && (
<>
<form onSubmit={(e) => handleUpdate(e, data._id)}>
<select name="completed">
<option value="true">Completed</option>
<option value="false">Not Completed</option>
</select>
<input type="submit" />
</form>
</>
)}
<button onClick={() => handleDelete(data._id)}>delete</button>
</div>
);
})}
</div>
);
};
זו המבנה הבסיסי. הצגנו את השם, הסופר והמצב, בין עם כפתורי עדכון ומחיקה.
עכשיו, בואו נוסף את הפעילויות.
import { useState } from "react";
import { book } from "../types/book.type";
import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";
import { Id } from "../../convex/_generated/dataModel";
import "../styles/book.css";
export const Books = ({ books }: { books: book[] }) => {
const [update, setUpdate] = useState(false);
const [id, setId] = useState("");
const deleteBooks = useMutation(api.queries.deleteBooks);
const updateStatus = useMutation(api.queries.updateStatus);
const handleClick = (id: string) => {
setId(id);
setUpdate(!update);
};
const handleDelete = (id: string) => {
deleteBooks({ id: id as Id<"books"> })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
};
const handleUpdate = (e: React.FormEvent<HTMLFormElement>, id: string) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
const isCompleted: boolean =
(formdata.get("completed") as string) === "true";
updateStatus({ id: id as Id<"books">, isCompleted })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
setUpdate(false);
};
return (
<div>
{books.map((data: book, index: number) => {
return (
<div
key={data._id}
className={`book-container ${data.isCompleted ? "completed" : "not-completed"}`}
>
<h3>Book no: {index + 1}</h3>
<p>Book title: {data.title}</p>
<p>Book Author: {data.author}</p>
<p>
Completed Status:{" "}
{data.isCompleted ? "Completed" : "Not Completed"}
</p>
<button onClick={() => handleClick(data._id)}>Update</button>
{id === data._id && update && (
<>
<form onSubmit={(e) => handleUpdate(e, data._id)}>
<select name="completed">
<option value="true">Completed</option>
<option value="false">Not Completed</option>
</select>
<input type="submit" />
</form>
</>
)}
<button onClick={() => handleDelete(data._id)}>delete</button>
</div>
);
})}
</div>
);
};
זה כל קוד הרכב.
ראשית, אנו צריכים להפעיל את העדכון, ולכן הגדרנו את פונקציית handleClick
והעברנו אליה מזהה מסמך.
//handleClick
const handleClick = (id: string) => {
setId(id);
setUpdate(!update);
};
בפונקציית handleClick
ניתן לעדכן את מצב המזהה ולהפעיל את מצב העדכון כך שכאשר נלחץ, יופעל קלט העדכון, ובקליק נוסף, הוא ייסגר.
לאחר מכן, יש לנו את handleUpdate
. אנו צריכים את מזהה המסמך כדי לעדכן את הנתונים, ולכן העברנו את אובייקט האירוע וגם את מזהה המסמך. כדי לקבל את הקלט, נוכל להשתמש ב-FormData
.
const updateStatus = useMutation(api.queries.updateStatus);
const handleUpdate = (e: React.FormEvent<HTMLFormElement>, id: string) => {
e.preventDefault();
const formdata = new FormData(e.currentTarget);
const isCompleted: boolean =
(formdata.get("completed") as string) === "true";
updateStatus({ id: id as Id<"books">, isCompleted })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
setUpdate(false);
};
אנו צריכים להשתמש ב-useMutation
כדי לקבל את פונקציית updateStatus
. העבר את המזהה ואת מצב ההשלמה לפונקציה, וטפל בחלק האסינכרוני באמצעות .then
לגבי פונקציית המחיקה, מזהה המסמך מספיק. כמו הקודמת, קרא לפונקציית המחיקה באמצעות useMutation
והעבר את המזהה אליה.
לאחר מכן העבר את מזהה המסמך וטפל בהבטחה.
const deleteBooks = useMutation(api.queries.deleteBooks);
const handleDelete = (id: string) => {
deleteBooks({ id: id as Id<"books"> })
.then((mess) => console.log(mess))
.catch((err) => console.log(err));
};
עיצוב
לבסוף, מה שנותר הוא להוסיף קצת עיצוב. הוספתי עיצוב בסיסי. אם הספר לא הושלם, הוא יהיה באדום, ואם הספר הושלם, הוא יהיה בירוק.
הנה צילום המסך:
זה הכל חברים!!
אתם יכולים לבדוק את המאגר שלי לקוד המלא: convex-curd
סיכום
במאמר זה, יישמנו את הפעילויות CRUD (יצירה, קריאה, עדכון, מחיקה) על-ידי בניית אפליקציית אוסף ספרים. אנחנו מתחילים בהגדרת Convex וReact, וכתיבת ההגיון CRUD.
הורשע הדרכה עבד בעבר של קידמה וסוף, מראה איך לבנות אפליקציה סרבלוסינג.
ניתן למצוא את כל הקוד כאן: convex-curd
אם יש שגיאות או ספקים, תיקשרו אליי ב LinkedIn, Instagram.
תודה רבה על הקריאה!
Source:
https://www.freecodecamp.org/news/build-crud-app-react-and-convex/