|
124 | 124 |
|
125 | 125 |  |
126 | 126 |
|
| 127 | +### 02.15 |
| 128 | + |
| 129 | +> 学习时间:120 min |
| 130 | +
|
| 131 | +cfc-web 项目开始要用到后端了。我暂时懒得去搭建后端服务,决定在 `convex` 和 `sanity` 中选择一个使用,下面是它们之间的对比: |
| 132 | + |
| 133 | +**核心定位** |
| 134 | +|工具|类型|主要用途| |
| 135 | +|--|--|--| |
| 136 | +|Convex|实时后端即服务(BaaS)|提供实时数据库、函数即服务(FaaS)、状态管理,用于快速构建全栈实时应用。| |
| 137 | +|Sanity|内容管理系统(CMS)|专注于内容管理和结构化数据存储,提供灵活的内容建模和编辑界面。| |
| 138 | + |
| 139 | +考虑到目前最高优先级是实现表单提交并储存的功能,而 `Sanity` 更适合博客或文档这类的内容管理,于是我决定使用 `Convex`。 |
| 140 | + |
| 141 | +但是 `Convex` 也存在一个缺点,它不是开源的,若持续使用可能会产生费用。若需要开源方案可以选择 `Supabase`,但因为之前使用过 `Supabase`,我又想尝试一些新东西,所以还是用用 `Convex` 吧,等到了收费那一步在做迁移吧(虽然迁移难度应该挺大)。 |
| 142 | + |
| 143 | +--- |
| 144 | + |
| 145 | +**走一遍 Convex 官方的教程** |
| 146 | + |
| 147 | +初始化项目: |
| 148 | + |
| 149 | +```shell |
| 150 | +npm install convex |
| 151 | + |
| 152 | +npx convex dev |
| 153 | +``` |
| 154 | + |
| 155 | +创建实例数据 `sampleData.jsonl` |
| 156 | + |
| 157 | +```jsonl |
| 158 | +{"text": "Buy groceries", "isCompleted": true} |
| 159 | +{"text": "Go for a swim", "isCompleted": true} |
| 160 | +{"text": "Integrate Convex", "isCompleted": false} |
| 161 | +``` |
| 162 | + |
| 163 | +通过以下方式添加到数据库中: |
| 164 | + |
| 165 | +```shell |
| 166 | +npx convex import --table tasks sampleData.jsonl |
| 167 | +``` |
| 168 | + |
| 169 | +可以在网页中看到数据 |
| 170 | + |
| 171 | + |
| 172 | + |
| 173 | +通过下面的方式定义一个查询 api,用于查询 tasks 库中的内容。 |
| 174 | + |
| 175 | +```typescript |
| 176 | +import { query } from "./_generated/server"; |
| 177 | + |
| 178 | +export const get = query({ |
| 179 | + args: {}, |
| 180 | + handler: async (ctx) => { |
| 181 | + return await ctx.db.query("tasks").collect(); |
| 182 | + }, |
| 183 | +}); |
| 184 | +``` |
| 185 | + |
| 186 | +在查询前需要创建一个 provider(常规操作了) |
| 187 | + |
| 188 | +```tsx |
| 189 | +"use client"; |
| 190 | + |
| 191 | +import { ConvexProvider, ConvexReactClient } from "convex/react"; |
| 192 | +import { ReactNode } from "react"; |
| 193 | + |
| 194 | +const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); |
| 195 | + |
| 196 | +export function ConvexClientProvider({ children }: { children: ReactNode }) { |
| 197 | + return <ConvexProvider client={convex}>{children}</ConvexProvider>; |
| 198 | +} |
| 199 | +``` |
| 200 | + |
| 201 | +privider 需要包裹在 RootLayout 下: |
| 202 | + |
| 203 | +```tsx |
| 204 | +"use client"; |
| 205 | + |
| 206 | +import { ConvexProvider, ConvexReactClient } from "convex/react"; |
| 207 | +import { ReactNode } from "react"; |
| 208 | + |
| 209 | +const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!); |
| 210 | + |
| 211 | +export function ConvexClientProvider({ children }: { children: ReactNode }) { |
| 212 | + return <ConvexProvider client={convex}>{children}</ConvexProvider>; |
| 213 | +} |
| 214 | + |
| 215 | +{/* */} |
| 216 | + |
| 217 | +import { ThirdwebProvider } from "thirdweb/react"; |
| 218 | +import { ConvexClientProvider } from "./convex-client-provider"; |
| 219 | + |
| 220 | +export function Providers({ |
| 221 | + children, |
| 222 | +}: Readonly<{ |
| 223 | + children: React.ReactNode; |
| 224 | +}>) { |
| 225 | + return ( |
| 226 | + <ThirdwebProvider> |
| 227 | + <ConvexClientProvider>{children}</ConvexClientProvider> |
| 228 | + </ThirdwebProvider> |
| 229 | + ); |
| 230 | +} |
| 231 | +``` |
| 232 | + |
| 233 | +下面是查询的方式 |
| 234 | + |
| 235 | +```tsx |
| 236 | +"use client"; |
| 237 | + |
| 238 | +import Image from "next/image"; |
| 239 | +import { useQuery } from "convex/react"; |
| 240 | +import { api } from "../convex/_generated/api"; |
| 241 | + |
| 242 | +export default function Home() { |
| 243 | + const tasks = useQuery(api.tasks.get); |
| 244 | + return ( |
| 245 | + <main className="flex min-h-screen flex-col items-center justify-between p-24"> |
| 246 | + {tasks?.map(({ _id, text }) => <div key={_id}>{text}</div>)} |
| 247 | + </main> |
| 248 | + ); |
| 249 | +} |
| 250 | +``` |
| 251 | + |
| 252 | +--- |
| 253 | + |
| 254 | +**Convex 数据库概念** |
| 255 | + |
| 256 | +> Convex 数据库采用关系型数据模型设计,支持类 JSON 文档存储,可自由选择是否使用模式定义。其"开箱即用"的特性,通过简洁易用的接口即可实现稳定的查询性能。 |
| 257 | +> |
| 258 | +> 通过轻量级 JavaScript API,您可以直接在查询和变更函数中实现数据读写操作。无需任何配置,无需编写 SQL 语句,仅需使用 JavaScript 即可满足应用程序的所有数据需求。 |
| 259 | +
|
| 260 | +**Tables** |
| 261 | + |
| 262 | +`Conve`x 中,数据库表不需要事先指定结构,插入内容之后表结构会自行创建。 |
| 263 | + |
| 264 | +```ts |
| 265 | +// `friends` table doesn't exist. |
| 266 | +await ctx.db.insert("friends", { name: "Jamie" }); |
| 267 | +// Now it does, and it has one document. |
| 268 | +``` |
| 269 | + |
| 270 | +**Documents** |
| 271 | + |
| 272 | +表中的内容被称为 `Documents`,它是一个 `json` 对象,类似 `mongoDB` 中的数据。 |
| 273 | + |
| 274 | +> 这里有一种 .jsonl 文件,我的理解是它是一些又 json 对象组成的列表 |
| 275 | +
|
| 276 | +```jsonl |
| 277 | +{} |
| 278 | +{"name": "Jamie"} |
| 279 | +{"name": {"first": "Ari", "second": "Cole"}, "age": 60} |
| 280 | +``` |
| 281 | + |
| 282 | +**Schemas** |
| 283 | + |
| 284 | +若实在需要定义表结构,可以使用 `Schemas`: |
| 285 | + |
| 286 | +```ts |
| 287 | +import { defineSchema, defineTable } from "convex/server"; |
| 288 | +import { v } from "convex/values"; |
| 289 | + |
| 290 | +// @snippet start schema |
| 291 | +export default defineSchema({ |
| 292 | + messages: defineTable({ |
| 293 | + author: v.id("users"), |
| 294 | + body: v.string(), |
| 295 | + }), |
| 296 | +}); |
| 297 | +``` |
| 298 | + |
| 299 | +--- |
| 300 | + |
| 301 | +**写入数据** |
| 302 | + |
| 303 | +使用 `db.insert` 方法在数据库中创建新 `Document`,`insert` 会返回插入结果的 `id`: |
| 304 | + |
| 305 | +```ts |
| 306 | +import { mutation } from "./_generated/server"; |
| 307 | +import { v } from "convex/values"; |
| 308 | + |
| 309 | +export const createTask = mutation({ |
| 310 | + args: { text: v.string() }, |
| 311 | + handler: async (ctx, args) => { |
| 312 | + const taskId = await ctx.db.insert("tasks", { text: args.text }); |
| 313 | + // do something with `taskId` |
| 314 | + }, |
| 315 | +}); |
| 316 | +``` |
| 317 | + |
| 318 | +使用 `db.patch` 方法更新现有 `Document`: |
| 319 | + |
| 320 | +```ts |
| 321 | +import { mutation } from "./_generated/server"; |
| 322 | +import { v } from "convex/values"; |
| 323 | + |
| 324 | +export const updateTask = mutation({ |
| 325 | + args: { id: v.id("tasks") }, |
| 326 | + handler: async (ctx, args) => { |
| 327 | + const { id } = args; |
| 328 | + console.log(await ctx.db.get(id)); |
| 329 | + // { text: "foo", status: { done: true }, _id: ... } |
| 330 | + |
| 331 | + // Add `tag` and overwrite `status`: |
| 332 | + await ctx.db.patch(id, { tag: "bar", status: { archived: true } }); |
| 333 | + console.log(await ctx.db.get(id)); |
| 334 | + // { text: "foo", tag: "bar", status: { archived: true }, _id: ... } |
| 335 | + |
| 336 | + // Unset `tag` by setting it to `undefined` |
| 337 | + await ctx.db.patch(id, { tag: undefined }); |
| 338 | + console.log(await ctx.db.get(id)); |
| 339 | + // { text: "foo", status: { archived: true }, _id: ... } |
| 340 | + }, |
| 341 | +}); |
| 342 | +``` |
| 343 | + |
| 344 | +使用 `db.delete` 方法删除现有 `Document`: |
| 345 | + |
| 346 | +```ts |
| 347 | +import { mutation } from "./_generated/server"; |
| 348 | +import { v } from "convex/values"; |
| 349 | + |
| 350 | +export const deleteTask = mutation({ |
| 351 | + args: { id: v.id("tasks") }, |
| 352 | + handler: async (ctx, args) => { |
| 353 | + await ctx.db.delete(args.id); |
| 354 | + }, |
| 355 | +}); |
| 356 | +``` |
| 357 | + |
| 358 | +--- |
| 359 | + |
| 360 | +**定义Schemas** |
| 361 | + |
| 362 | +在 `npm convex dev` 启动的情况下,创建 `convex/schema.ts` 文件,会自动在数据库中创建对应的表结构。 |
| 363 | + |
| 364 | +```ts |
| 365 | +import { defineSchema, defineTable } from "convex/server"; |
| 366 | +import { v } from "convex/values"; |
| 367 | + |
| 368 | +export default defineSchema({ |
| 369 | + messages: defineTable({ |
| 370 | + body: v.string(), |
| 371 | + user: v.id("users"), |
| 372 | + }), |
| 373 | + users: defineTable({ |
| 374 | + name: v.string(), |
| 375 | + tokenIdentifier: v.string(), |
| 376 | + }).index("by_token", ["tokenIdentifier"]), |
| 377 | +}); |
| 378 | +``` |
| 379 | + |
| 380 | +同时,在这种情况下使用未在 `schema.ts` 中定义的表会报错(例如在教程中创建的 `tasks` 表)。 |
| 381 | + |
| 382 | + |
| 383 | + |
| 384 | +> 总结,快速过了一下 convex 的教程,熟悉了插入与查询数据的方式,同时成功在 `cfc-web` 项目中实现报名表单数据的提交。 |
| 385 | +
|
127 | 386 | <!-- Content_END --> |
0 commit comments