studio-best-practices
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStudio Best Practices
Studio最佳实践
Applies to .
apps/studio/**/*.{ts,tsx}适用于 文件。
apps/studio/**/*.{ts,tsx}Boolean Naming
布尔值命名
Use descriptive prefixes — derive from existing state rather than storing separately:
- — state/identity:
is,isLoading,isPausedisNewRecord - — possession:
has,hasPermissionhasData - — capability:
can,canUpdateColumnscanDelete - — conditional behavior:
should,shouldFetchshouldRender
Extract complex conditions into named variables:
tsx
// ❌ inline multi-condition
{
!isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading && <Button />
}
// ✅ named variable
const canShowAddButton =
!isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading
{
canShowAddButton && <Button />
}Derive booleans — don't store them:
tsx
// ❌ 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('@')使用具有描述性的前缀,从现有状态派生而不是单独存储:
- 表示状态/身份:
is、isLoading、isPausedisNewRecord - 表示持有属性:
has、hasPermissionhasData - 表示具备能力:
can、canUpdateColumnscanDelete - 表示条件行为:
should、shouldFetchshouldRender
将复杂条件提取为命名变量:
tsx
// ❌ 行内多条件判断
{
!isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading && <Button />
}
// ✅ 命名变量
const canShowAddButton =
!isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading
{
canShowAddButton && <Button />
}派生布尔值,不要主动存储:
tsx
// ❌ 存储派生状态
const [isFormValid, setIsFormValid] = useState(false)
useEffect(() => {
setIsFormValid(name.length > 0 && email.includes('@'))
}, [name, email])
// ✅ 直接派生
const isFormValid = name.length > 0 && email.includes('@')Component Structure
组件结构
See skill for compound component and composition patterns.
vercel-composition-patternsKeep components under 200–300 lines. Split when you see:
- Multiple distinct UI sections
- Complex conditional rendering
- Multiple unrelated calls
useState - Hard to understand at a glance
Co-locate sub-components in the same directory as the parent. Avoid barrel re-export files.
Extract repeated JSX patterns into small components.
参考 skill 了解复合组件和组合模式。
vercel-composition-patterns组件代码长度保持在200-300行以内,出现以下情况时考虑拆分:
- 多个独立的UI模块
- 复杂的条件渲染逻辑
- 多个无关联的调用
useState - 代码难以一眼理解
将子组件和父组件放在同一目录下,避免桶文件(barrel re-export)的重导出。
将重复的JSX模式提取为小组件。
Data Fetching
数据请求
All data fetching uses TanStack Query (React Query). See skill for query/mutation patterns and skill for error display conventions.
studio-queriesstudio-error-handling所有数据请求使用TanStack Query(React Query)。参考 skill 了解查询/变更模式,参考 skill 了解错误展示规范。
studio-queriesstudio-error-handlingLoading / Error / Success Pattern
加载/错误/成功模式
Top level:
tsx
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} />Use early returns — avoid deeply nested conditionals.
Inline:
tsx
<div>
{isLoading && <InlineLoader />}
{isError && <InlineError error={error} />}
{isSuccess && data.length === 0 && <EmptyState />}
{isSuccess && data.length > 0 && <DataDisplay data={data} />}
</div>顶层写法:
tsx
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} />使用提前返回,避免深层嵌套的条件语句。
行内写法:
tsx
<div>
{isLoading && <InlineLoader />}
{isError && <InlineError error={error} />}
{isSuccess && data.length === 0 && <EmptyState />}
{isSuccess && data.length > 0 && <DataDisplay data={data} />}
</div>State Management
状态管理
Keep state as local as possible; lift only when needed.
Group related form state with rather than multiple calls. See skill for form layout and component conventions.
react-hook-formuseStatestudio-ui-patternstsx
// ❌ multiple related useState
const [name, setName] = useState('')
const [email, setEmail] = useState('')
// ✅ grouped with react-hook-form
const form = useForm<FormValues>({ defaultValues: { name: '', email: '' } })尽可能保持状态本地化,仅在必要时提升状态。
使用分组管理关联的表单状态,而不是多个调用。参考 skill 了解表单布局和组件规范。
react-hook-formuseStatestudio-ui-patternstsx
// ❌ 多个关联的useState
const [name, setName] = useState('')
const [email, setEmail] = useState('')
// ✅ 使用react-hook-form分组
const form = useForm<FormValues>({ defaultValues: { name: '', email: '' } })Custom Hooks
自定义Hook
Extract complex or reusable logic into hooks. Return objects, not arrays:
tsx
// ❌ array return (hard to extend)
return [value, toggle]
// ✅ object return
return { value, toggle, setTrue, setFalse }将复杂或可复用的逻辑提取为Hook,返回对象而非数组:
tsx
// ❌ 数组返回(难以扩展)
return [value, toggle]
// ✅ 对象返回
return { value, toggle, setTrue, setFalse }Event Handlers
事件处理器
- Prop callbacks: prefix (
on,onClose)onSave - Internal handlers: prefix (
handle,handleSubmit)handleCancel
Use for handlers passed to memoized children; avoid unnecessary inline arrow functions.
useCallback- 回调Prop:使用前缀(
on、onClose)onSave - 内部处理器:使用前缀(
handle、handleSubmit)handleCancel
向经过memo优化的子组件传递处理器时使用,避免不必要的行内箭头函数。
useCallbackConditional Rendering
条件渲染
tsx
// 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 />tsx
// 简单显示/隐藏
<>{isVisible && <Component />}</>
// 二选一渲染
<>{isLoading ? <Spinner /> : <Content />}</>
// 多条件场景:使用提前返回,不要嵌套三元运算符
if (isLoading) return <Spinner />
if (isError) return <Error />
return <Content />Performance
性能优化
useMemouseMemoTypeScript
TypeScript
Define prop interfaces explicitly. Use discriminated unions for complex state:
tsx
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }Avoid / casts. Validate at boundaries with zod:
as anyas Typetsx
// ❌ type cast
const user = apiResponse as User
// ✅ zod parse
const user = userSchema.parse(apiResponse)
// or safe:
const result = userSchema.safeParse(apiResponse)明确定义Props接口,复杂状态使用可辨识联合类型:
tsx
type AsyncState<T> =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: T }
| { status: 'error'; error: Error }避免使用/类型断言,在边界处使用zod做数据校验:
as anyas Typetsx
// ❌ 类型断言
const user = apiResponse as User
// ✅ zod校验解析
const user = userSchema.parse(apiResponse)
// 或者安全解析:
const result = userSchema.safeParse(apiResponse)Testing
测试
Extract logic into pure functions and test exhaustively. See the skill for the full testing strategy and decision tree.
.utils.tsstudio-testing将逻辑提取到的纯函数中并进行全面测试。参考 skill 了解完整的测试策略和决策树。
.utils.tsstudio-testing