convex-quickstart

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Convex Quickstart

Convex 快速入门

Get a production-ready Convex backend set up in minutes. This skill guides you through initializing Convex, creating your schema, setting up auth, and building your first CRUD operations.
几分钟内搭建好可用于生产环境的 Convex 后端。本指南将引导你完成 Convex 初始化、schema 创建、认证设置以及首个 CRUD 操作的构建。

When to Use

适用场景

  • Starting a brand new project with Convex
  • Adding Convex to an existing React/Next.js app
  • Prototyping a new feature with real-time data
  • Converting from another backend to Convex
  • Teaching someone Convex for the first time
  • 使用 Convex 启动全新项目
  • 为现有 React/Next.js 应用添加 Convex
  • 基于实时数据原型化新功能
  • 从其他后端迁移至 Convex
  • 首次向他人传授 Convex 使用方法

Prerequisites Check

前置检查

Before starting, verify:
bash
node --version  # v18 or higher
npm --version   # v8 or higher
开始前,请确认:
bash
node --version  # v18 或更高版本
npm --version   # v8 或更高版本

Quick Start Flow

快速入门流程

Step 1: Install and Initialize

步骤1:安装与初始化

bash
undefined
bash
undefined

Install Convex

安装 Convex

npm install convex
npm install convex

Initialize (creates convex/ directory)

初始化(创建 convex/ 目录)

npx convex dev

This command:
- Creates `convex/` directory
- Sets up authentication
- Starts development server
- Generates TypeScript types
npx convex dev

该命令会:
- 创建 `convex/` 目录
- 配置认证
- 启动开发服务器
- 生成 TypeScript 类型定义

Step 2: Create Schema

步骤2:创建 Schema

Create
convex/schema.ts
:
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    tokenIdentifier: v.string(),
    name: v.string(),
    email: v.string(),
  }).index("by_token", ["tokenIdentifier"]),

  // Add your tables here
  // Example: Tasks table
  tasks: defineTable({
    userId: v.id("users"),
    title: v.string(),
    completed: v.boolean(),
    createdAt: v.number(),
  })
    .index("by_user", ["userId"])
    .index("by_user_and_completed", ["userId", "completed"]),
});
创建
convex/schema.ts
typescript
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  users: defineTable({
    tokenIdentifier: v.string(),
    name: v.string(),
    email: v.string(),
  }).index("by_token", ["tokenIdentifier"]),

  // 在此添加你的表
  // 示例:任务表
  tasks: defineTable({
    userId: v.id("users"),
    title: v.string(),
    completed: v.boolean(),
    createdAt: v.number(),
  })
    .index("by_user", ["userId"])
    .index("by_user_and_completed", ["userId", "completed"]),
});

Step 3: Set Up Authentication

步骤3:配置认证

We'll use WorkOS AuthKit, which provides a complete auth solution with minimal setup.
bash
npm install @workos-inc/authkit-react
我们将使用 WorkOS AuthKit,它能以极少的配置提供完整的认证解决方案。
bash
npm install @workos-inc/authkit-react

For React/Vite Apps:

针对 React/Vite 应用:

typescript
// src/main.tsx
import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

// Configure Convex to use WorkOS auth
convex.setAuth(useAuth);

function App() {
  return (
    <AuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}>
      <ConvexProvider client={convex}>
        <YourApp />
      </ConvexProvider>
    </AuthKitProvider>
  );
}
typescript
// src/main.tsx
import { AuthKitProvider, useAuth } from "@workos-inc/authkit-react";
import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";

const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL);

// 配置 Convex 使用 WorkOS 认证
convex.setAuth(useAuth);

function App() {
  return (
    <AuthKitProvider clientId={import.meta.env.VITE_WORKOS_CLIENT_ID}>
      <ConvexProvider client={convex}>
        <YourApp />
      </ConvexProvider>
    </AuthKitProvider>
  );
}

For Next.js Apps:

针对 Next.js 应用:

bash
npm install @workos-inc/authkit-nextjs
typescript
// app/layout.tsx
import { AuthKitProvider } from "@workos-inc/authkit-nextjs";
import { ConvexClientProvider } from "./ConvexClientProvider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <AuthKitProvider>
          <ConvexClientProvider>
            {children}
          </ConvexClientProvider>
        </AuthKitProvider>
      </body>
    </html>
  );
}
typescript
// app/ConvexClientProvider.tsx
"use client";

import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";
import { useAuth } from "@workos-inc/authkit-nextjs";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
  const { getToken } = useAuth();

  convex.setAuth(async () => {
    return await getToken();
  });

  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}
bash
npm install @workos-inc/authkit-nextjs
typescript
// app/layout.tsx
import { AuthKitProvider } from "@workos-inc/authkit-nextjs";
import { ConvexClientProvider } from "./ConvexClientProvider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <AuthKitProvider>
          <ConvexClientProvider>
            {children}
          </ConvexClientProvider>
        </AuthKitProvider>
      </body>
    </html>
  );
}
typescript
// app/ConvexClientProvider.tsx
"use client";

import { ConvexReactClient } from "convex/react";
import { ConvexProvider } from "convex/react";
import { useAuth } from "@workos-inc/authkit-nextjs";

const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export function ConvexClientProvider({ children }: { children: React.ReactNode }) {
  const { getToken } = useAuth();

  convex.setAuth(async () => {
    return await getToken();
  });

  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
}

Environment Variables:

环境变量:

bash
undefined
bash
undefined

.env.local

.env.local

VITE_CONVEX_URL=https://your-deployment.convex.cloud VITE_WORKOS_CLIENT_ID=your_workos_client_id
VITE_CONVEX_URL=https://your-deployment.convex.cloud VITE_WORKOS_CLIENT_ID=your_workos_client_id

For Next.js:

针对 Next.js:

NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud NEXT_PUBLIC_WORKOS_CLIENT_ID=your_workos_client_id WORKOS_API_KEY=your_workos_api_key WORKOS_COOKIE_PASSWORD=generate_a_random_32_character_string

**Alternative auth providers:** If you need to use a different provider (Clerk, Auth0, custom JWT), see the [Convex auth documentation](https://docs.convex.dev/auth).
NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud NEXT_PUBLIC_WORKOS_CLIENT_ID=your_workos_client_id WORKOS_API_KEY=your_workos_api_key WORKOS_COOKIE_PASSWORD=generate_a_random_32_character_string

**替代认证提供商:** 如果你需要使用其他提供商(Clerk、Auth0、自定义 JWT),请查看 [Convex 认证文档](https://docs.convex.dev/auth)。

Step 4: Create Auth Helpers

步骤4:创建认证工具函数

Create
convex/lib/auth.ts
:
typescript
import { QueryCtx, MutationCtx } from "../_generated/server";
import { Doc } from "../_generated/dataModel";

export async function getCurrentUser(
  ctx: QueryCtx | MutationCtx
): Promise<Doc<"users">> {
  const identity = await ctx.auth.getUserIdentity();
  if (!identity) {
    throw new Error("Not authenticated");
  }

  const user = await ctx.db
    .query("users")
    .withIndex("by_token", q =>
      q.eq("tokenIdentifier", identity.tokenIdentifier)
    )
    .unique();

  if (!user) {
    throw new Error("User not found");
  }

  return user;
}
Create
convex/users.ts
:
typescript
import { mutation } from "./_generated/server";

export const store = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");

    const existing = await ctx.db
      .query("users")
      .withIndex("by_token", q =>
        q.eq("tokenIdentifier", identity.tokenIdentifier)
      )
      .unique();

    if (existing) return existing._id;

    return await ctx.db.insert("users", {
      tokenIdentifier: identity.tokenIdentifier,
      name: identity.name ?? "Anonymous",
      email: identity.email ?? "",
    });
  },
});
创建
convex/lib/auth.ts
typescript
import { QueryCtx, MutationCtx } from "../_generated/server";
import { Doc } from "../_generated/dataModel";

export async function getCurrentUser(
  ctx: QueryCtx | MutationCtx
): Promise<Doc<"users">> {
  const identity = await ctx.auth.getUserIdentity();
  if (!identity) {
    throw new Error("未认证");
  }

  const user = await ctx.db
    .query("users")
    .withIndex("by_token", q =>
      q.eq("tokenIdentifier", identity.tokenIdentifier)
    )
    .unique();

  if (!user) {
    throw new Error("用户未找到");
  }

  return user;
}
创建
convex/users.ts
typescript
import { mutation } from "./_generated/server";

export const store = mutation({
  args: {},
  handler: async (ctx) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("未认证");

    const existing = await ctx.db
      .query("users")
      .withIndex("by_token", q =>
        q.eq("tokenIdentifier", identity.tokenIdentifier)
      )
      .unique();

    if (existing) return existing._id;

    return await ctx.db.insert("users", {
      tokenIdentifier: identity.tokenIdentifier,
      name: identity.name ?? "匿名",
      email: identity.email ?? "",
    });
  },
});

Step 5: Create Your First CRUD Operations

步骤5:创建首个 CRUD 操作

Create
convex/tasks.ts
:
typescript
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getCurrentUser } from "./lib/auth";

// List all tasks for current user
export const list = query({
  args: {},
  handler: async (ctx) => {
    const user = await getCurrentUser(ctx);
    return await ctx.db
      .query("tasks")
      .withIndex("by_user", q => q.eq("userId", user._id))
      .order("desc")
      .collect();
  },
});

// Get a single task
export const get = query({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("Task not found");
    if (task.userId !== user._id) throw new Error("Unauthorized");

    return task;
  },
});

// Create a task
export const create = mutation({
  args: { title: v.string() },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    return await ctx.db.insert("tasks", {
      userId: user._id,
      title: args.title,
      completed: false,
      createdAt: Date.now(),
    });
  },
});

// Update a task
export const update = mutation({
  args: {
    taskId: v.id("tasks"),
    title: v.optional(v.string()),
    completed: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("Task not found");
    if (task.userId !== user._id) throw new Error("Unauthorized");

    const updates: any = {};
    if (args.title !== undefined) updates.title = args.title;
    if (args.completed !== undefined) updates.completed = args.completed;

    await ctx.db.patch(args.taskId, updates);
  },
});

// Delete a task
export const remove = mutation({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("Task not found");
    if (task.userId !== user._id) throw new Error("Unauthorized");

    await ctx.db.delete(args.taskId);
  },
});
创建
convex/tasks.ts
typescript
import { query, mutation } from "./_generated/server";
import { v } from "convex/values";
import { getCurrentUser } from "./lib/auth";

// 列出当前用户的所有任务
export const list = query({
  args: {},
  handler: async (ctx) => {
    const user = await getCurrentUser(ctx);
    return await ctx.db
      .query("tasks")
      .withIndex("by_user", q => q.eq("userId", user._id))
      .order("desc")
      .collect();
  },
});

// 获取单个任务
export const get = query({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("任务未找到");
    if (task.userId !== user._id) throw new Error("无权限");

    return task;
  },
});

// 创建任务
export const create = mutation({
  args: { title: v.string() },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    return await ctx.db.insert("tasks", {
      userId: user._id,
      title: args.title,
      completed: false,
      createdAt: Date.now(),
    });
  },
});

// 更新任务
export const update = mutation({
  args: {
    taskId: v.id("tasks"),
    title: v.optional(v.string()),
    completed: v.optional(v.boolean()),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("任务未找到");
    if (task.userId !== user._id) throw new Error("无权限");

    const updates: any = {};
    if (args.title !== undefined) updates.title = args.title;
    if (args.completed !== undefined) updates.completed = args.completed;

    await ctx.db.patch(args.taskId, updates);
  },
});

// 删除任务
export const remove = mutation({
  args: { taskId: v.id("tasks") },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);
    const task = await ctx.db.get(args.taskId);

    if (!task) throw new Error("任务未找到");
    if (task.userId !== user._id) throw new Error("无权限");

    await ctx.db.delete(args.taskId);
  },
});

Step 6: Use in Your React App

步骤6:在 React 应用中使用

typescript
// app/tasks/page.tsx
"use client";

import { useQuery, useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";

export default function TasksPage() {
  const tasks = useQuery(api.tasks.list);
  const create = useMutation(api.tasks.create);
  const update = useMutation(api.tasks.update);
  const remove = useMutation(api.tasks.remove);

  if (!tasks) return <div>Loading...</div>;

  return (
    <div>
      <h1>Tasks</h1>

      {/* Create task */}
      <form onSubmit={(e) => {
        e.preventDefault();
        const formData = new FormData(e.target as HTMLFormElement);
        create({ title: formData.get("title") as string });
        (e.target as HTMLFormElement).reset();
      }}>
        <input name="title" placeholder="New task" required />
        <button type="submit">Add</button>
      </form>

      {/* Task list */}
      {tasks.map(task => (
        <div key={task._id}>
          <input
            type="checkbox"
            checked={task.completed}
            onChange={(e) => update({
              taskId: task._id,
              completed: e.target.checked
            })}
          />
          <span>{task.title}</span>
          <button onClick={() => remove({ taskId: task._id })}>
            Delete
          </button>
        </div>
      ))}
    </div>
  );
}
typescript
// app/tasks/page.tsx
"use client";

import { useQuery, useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";

export default function TasksPage() {
  const tasks = useQuery(api.tasks.list);
  const create = useMutation(api.tasks.create);
  const update = useMutation(api.tasks.update);
  const remove = useMutation(api.tasks.remove);

  if (!tasks) return <div>加载中...</div>;

  return (
    <div>
      <h1>任务列表</h1>

      {/* 创建任务 */}
      <form onSubmit={(e) => {
        e.preventDefault();
        const formData = new FormData(e.target as HTMLFormElement);
        create({ title: formData.get("title") as string });
        (e.target as HTMLFormElement).reset();
      }}>
        <input name="title" placeholder="新任务" required />
        <button type="submit">添加</button>
      </form>

      {/* 任务列表 */}
      {tasks.map(task => (
        <div key={task._id}>
          <input
            type="checkbox"
            checked={task.completed}
            onChange={(e) => update({
              taskId: task._id,
              completed: e.target.checked
            })}
          />
          <span>{task.title}</span>
          <button onClick={() => remove({ taskId: task._id })}>
            删除
          </button>
        </div>
      ))}
    </div>
  );
}

Step 7: Development vs Production

步骤7:开发环境 vs 生产环境

For Development (use this!):
bash
undefined
开发环境(推荐使用!):
bash
undefined

Start development server (NOT production!)

启动开发服务器(请勿用于生产环境!)

npx convex dev
npx convex dev

This runs locally and auto-reloads on changes

本地运行,代码变更时自动重载

Use this for all development work

所有开发工作都请使用该命令


**For Production Deployment:**
```bash

**生产环境部署:**
```bash

ONLY use this when deploying to production!

仅在部署生产环境时使用!

npx convex deploy
npx convex deploy

WARNING: This deploys to your production environment

警告:该命令会部署至你的生产环境

Don't use this during development

开发期间请勿使用


**Important:** Always use `npx convex dev` during development. Only use `npx convex deploy` when you're ready to ship to production.

**重要提示:** 开发期间请始终使用 `npx convex dev`。只有在准备好发布生产版本时,才使用 `npx convex deploy`。

Common Patterns

常见模式

Paginated Queries

分页查询

typescript
export const listPaginated = query({
  args: {
    cursor: v.optional(v.string()),
    limit: v.number(),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    const results = await ctx.db
      .query("tasks")
      .withIndex("by_user", q => q.eq("userId", user._id))
      .order("desc")
      .paginate({ cursor: args.cursor, limit: args.limit });

    return results;
  },
});
typescript
export const listPaginated = query({
  args: {
    cursor: v.optional(v.string()),
    limit: v.number(),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    const results = await ctx.db
      .query("tasks")
      .withIndex("by_user", q => q.eq("userId", user._id))
      .order("desc")
      .paginate({ cursor: args.cursor, limit: args.limit });

    return results;
  },
});

Filtering by Status

按状态筛选

typescript
export const listByStatus = query({
  args: {
    completed: v.boolean(),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    return await ctx.db
      .query("tasks")
      .withIndex("by_user_and_completed", q =>
        q.eq("userId", user._id).eq("completed", args.completed)
      )
      .collect();
  },
});
typescript
export const listByStatus = query({
  args: {
    completed: v.boolean(),
  },
  handler: async (ctx, args) => {
    const user = await getCurrentUser(ctx);

    return await ctx.db
      .query("tasks")
      .withIndex("by_user_and_completed", q =>
        q.eq("userId", user._id).eq("completed", args.completed)
      )
      .collect();
  },
});

Scheduled Jobs

定时任务

typescript
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

crons.daily(
  "cleanup-old-tasks",
  { hourUTC: 0, minuteUTC: 0 },
  internal.tasks.cleanupOld
);

export default crons;

// convex/tasks.ts
export const cleanupOld = internalMutation({
  handler: async (ctx) => {
    const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
    const oldTasks = await ctx.db
      .query("tasks")
      .filter(q =>
        q.and(
          q.eq(q.field("completed"), true),
          q.lt(q.field("createdAt"), thirtyDaysAgo)
        )
      )
      .collect();

    for (const task of oldTasks) {
      await ctx.db.delete(task._id);
    }
  },
});
typescript
// convex/crons.ts
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

crons.daily(
  "cleanup-old-tasks",
  { hourUTC: 0, minuteUTC: 0 },
  internal.tasks.cleanupOld
);

export default crons;

// convex/tasks.ts
export const cleanupOld = internalMutation({
  handler: async (ctx) => {
    const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1000;
    const oldTasks = await ctx.db
      .query("tasks")
      .filter(q =>
        q.and(
          q.eq(q.field("completed"), true),
          q.lt(q.field("createdAt"), thirtyDaysAgo)
        )
      )
      .collect();

    for (const task of oldTasks) {
      await ctx.db.delete(task._id);
    }
  },
});

Project Templates

项目模板

React + Vite + Convex

React + Vite + Convex

bash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex dev
bash
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install convex @workos-inc/authkit-react
npx convex dev

Next.js + Convex

Next.js + Convex

bash
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev
bash
npx create-next-app@latest my-app
cd my-app
npm install convex @workos-inc/authkit-nextjs
npx convex dev

Expo (React Native) + Convex

Expo(React Native)+ Convex

bash
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev
bash
npx create-expo-app my-app
cd my-app
npm install convex
npx convex dev

Checklist

检查清单

  • npm install convex
    completed
  • npx convex dev
    running (use this, NOT deploy!)
  • Schema created with proper indexes
  • Auth provider configured (WorkOS/custom)
  • getCurrentUser
    helper implemented
  • User storage mutation created
  • CRUD operations with auth checks
  • Frontend integrated with hooks
  • Tested queries and mutations locally
  • When ready for production, use
    npx convex deploy
Remember: Use
npx convex dev
for all development work. Only use
npx convex deploy
when deploying to production!
  • 已完成
    npm install convex
  • npx convex dev
    正在运行(请使用该命令,而非 deploy!)
  • 已创建包含正确索引的 Schema
  • 已配置认证提供商(WorkOS/自定义)
  • 已实现
    getCurrentUser
    工具函数
  • 已创建用户存储 mutation
  • 已创建包含认证校验的 CRUD 操作
  • 前端已通过 hooks 集成
  • 已在本地测试查询和 mutation
  • 准备生产部署时,使用
    npx convex deploy
切记: 所有开发工作请使用
npx convex dev
。仅在部署生产环境时使用
npx convex deploy

Next Steps

后续步骤

After quickstart:
  1. Add more tables to schema
  2. Implement relationships between tables
  3. Add file storage (Convex supports file uploads)
  4. Set up vector search for AI features
  5. Add cron jobs for scheduled tasks
  6. Configure production environment variables
完成快速入门后:
  1. 为 Schema 添加更多表
  2. 实现表之间的关联关系
  3. 添加文件存储(Convex 支持文件上传)
  4. 为 AI 功能设置向量搜索
  5. 添加定时任务
  6. 配置生产环境变量

Troubleshooting

故障排除

"Not authenticated" errors

“未认证”错误

  • Ensure auth provider is configured
  • Call
    storeUser
    mutation on first sign-in
  • Check
    getCurrentUser
    is imported correctly
  • 确保认证提供商已正确配置
  • 首次登录时调用
    storeUser
    mutation
  • 检查
    getCurrentUser
    是否已正确导入

Types not updating

类型未更新

  • Run
    npx convex dev
    (regenerates types)
  • Restart TypeScript server in editor
  • 运行
    npx convex dev
    (会重新生成类型)
  • 重启编辑器中的 TypeScript 服务

Slow queries

查询缓慢

  • Add indexes to schema
  • Use
    .withIndex()
    instead of
    .filter()
  • Check query patterns in dashboard
  • 为 Schema 添加索引
  • 使用
    .withIndex()
    替代
    .filter()
  • 在控制台中检查查询模式

Learn More

了解更多