mcpField
S
studio-best-practices

studio-best-practices

github.com/supabase开发114.3k158 次安装今天更新
代码仓库

关于 studio-best-practices

面向 Supabase Studio TypeScript 和 React 开发者的代码规范指南,涵盖组件组织、数据获取、状态管理、性能与测试实践。

主要功能

  1. 01描述性布尔变量命名
  2. 02组件拆分与组织建议
  3. 03使用 TanStack Query 获取数据
  4. 04保持状态局部并规范表单管理
  5. 05TypeScript 边界校验与测试建议

适用场景

  1. 01整理 Studio 组件结构
  2. 02实现查询的加载与错误状态
  3. 03规范表单状态及事件处理
  4. 04编写可测试的纯逻辑函数

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/supabase/supabase --skill studio-best-practices

安装后在 Claude Code 中输入/studio-best-practices确认已加载。

详细介绍

Studio 最佳实践

适用于 apps/studio/**/*.{ts,tsx}。

布尔值命名

使用描述性前缀——从现有状态推导,而不是单独存储:

  • is — 状态/身份:isLoading、isPaused、isNewRecord
  • has — 拥有:hasPermission、hasData
  • can — 能力:canUpdateColumns、canDelete
  • should — 条件行为:shouldFetch、shouldRender

将复杂条件提取为命名变量:

// ❌ inline multi-condition
{
  !isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading && <Button />
}

// ✅ named variable
const canShowAddButton =
  !isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading
{
  canShowAddButton && <Button />
}

推导布尔值——不要存储:

// ❌ stored derived state
const [isFormValid, setIsFormValid] = useState(false)
useEffect(() => {
  setIsFormValid(name.length > 0 && email.includes('@'))
}, [name, email])

// ✅ derived
const isFormValid = name.length > 0 && email.includes('@')

组件结构

请参阅 vercel-composition-patterns 技能,了解复合组件和组合模式。

将组件控制在 200–300 行以内。遇到以下情况时进行拆分:

  • 存在多个不同的 UI 区块
  • 条件渲染复杂
  • 有多个互不相关的 useState 调用
  • 代码难以一眼看懂

将子组件与父组件放在同一目录中。避免使用 barrel 重新导出文件。

将重复的 JSX 模式提取为小型组件。

数据获取

所有数据获取都使用 TanStack Query(React Query)。请参阅 studio-queries 技能,了解查询/变更模式;参阅 studio-error-handling 技能,了解错误显示规范。

加载 / 错误 / 成功模式

顶层:

const { data, error, isLoading, isError, isSuccess } = useQuery(...)

if (isLoading) return <GenericSkeletonLoader />
if (isError) return <AlertError error={error} subject="Failed to load data" />
if (isSuccess && data.length === 0) return <EmptyState />
return <DataDisplay data={data} />

使用提前返回,避免深层嵌套的条件判断。

内联:

<div>
  {isLoading && <InlineLoader />}
  {isError && <InlineError error={error} />}
  {isSuccess && data.length === 0 && <EmptyState />}
  {isSuccess && data.length > 0 && <DataDisplay data={data} />}
</div>

状态管理

尽可能将状态保留在局部;仅在需要时才提升状态。

使用 react-hook-form 对相关表单状态进行分组,而不是调用多个 useState。请参阅 studio-ui-patterns 技能,了解表单布局和组件规范。

// ❌ multiple related useState
const [name, setName] = useState('')
const [email, setEmail] = useState('')

// ✅ grouped with react-hook-form
const form = useForm<FormValues>({ defaultValues: { name: '', email: '' } })

自定义 Hook

将复杂或可复用的逻辑提取到 Hook 中。返回对象,而不是数组:

// ❌ array return (hard to extend)
return [value, toggle]

// ✅ object return
return { value, toggle, setTrue, setFalse }

事件处理器

  • 属性回调:使用 on 前缀(onClose、onSave)
  • 内部处理器:使用 handle 前缀(handleSubmit、handleCancel)

传递给经过 memoization 的子组件的处理器应使用 useCallback;避免不必要的内联箭头函数。

条件渲染

// Simple show/hide
<>{isVisible && <Component />}</>

// Binary choice
<>{isLoading ? <Spinner /> : <Content />}</>

// Multiple conditions — use early returns, not nested ternaries
if (isLoading) return <Spinner />
if (isError) return <Error />
return <Content />

性能

只有在确实存在计算开销时(经过衡量,而非假设)才使用 useMemo。不要对所有内容都使用它——只有在通过测量确认存在性能问题,或需要向经过 memoization 的子组件传递值时,才进行优化。

TypeScript

显式定义属性接口。对于复杂状态,使用判别联合:

type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error }

避免使用 as any / as Type 类型断言。在边界处使用 zod 进行验证:

// ❌ type cast
const user = apiResponse as User

// ✅ zod parse
const user = userSchema.parse(apiResponse)
// or safe:
const result = userSchema.safeParse(apiResponse)

测试

将逻辑提取到 .utils.ts 纯函数中,并进行全面测试。请参阅 studio-testing 技能,了解完整的测试策略和决策树。

评论

支持 Markdown