数据设计
Overview
设计数据结构、数据库 schema、状态管理时必须遵循的原则。
核心原则: 先设计再编码,完整思考数据流,保持单一数据源。
When to Use
触发场景:
- •设计新的数据库表结构
- •设计前端状态管理方案
- •处理多个数据源之间的关系
- •级联筛选、关联查询等复杂数据交互
- •遇到状态不一致、竞态条件问题
原则 1: 先设计数据结构再写代码
流程:
- •画出数据关系图
- •明确每层数据有哪些字段
- •标注哪些字段可能缺失
- •设计映射函数时考虑上下文传递
示例:
code
Event (父级)
├── id
├── slug
├── title
└── markets[] (嵌套)
├── id
├── eventId ← 可能缺失!需要从父级传入
└── question
代码体现:
typescript
// 映射函数接收父级上下文
function mapToMarket(raw: MarketRaw, parentEventId?: string): Market {
return {
eventId: raw.eventId || parentEventId, // 处理缺失
// ...
}
}
原则 2: 完整思考数据流(生产到消费)
不只考虑「获取端」,还要考虑「使用端」的真实场景。
思考框架:
code
数据从哪来 → 存到哪 → 在哪展示 → 用户如何交互
示例:Activity 页面的 Event 筛选
- •获取端:activities 表有
event_slug字段 - •存储:events 表存储 event 详情(title、icon)
- •展示:筛选下拉框需要显示 title 和 icon
- •问题:events 表可能还没同步某些 slug
解决:设计降级策略
typescript
// 已关联的 events
const existingEvents = await db.from('events').select('*').in('slug', slugs)
// 未关联的 slugs,创建占位对象
const missingSlugs = slugs.filter(s => !existingEvents.has(s))
const placeholders = missingSlugs.map(slug => ({
slug,
title: null, // 降级显示 slug
icon: null,
}))
return [...existingEvents, ...placeholders]
原则 3: 单一数据源
复杂状态变更应有唯一的控制点,避免多处同时修改导致竞态。
反模式:
typescript
// ❌ 子组件内协调多个状态变更
onSelect={() => {
onLeaderChange(value) // 触发状态变更 A
onEventChange(null) // 触发状态变更 B(可能竞态!)
}}
正确模式:
typescript
// ✅ Container 统一管理,子组件只触发单一回调
// Container
const handleLeaderChange = useCallback((address) => {
setUrlState({ page: 1, leader: address, event: null }) // 一次性更新
}, [setUrlState])
// Filter 组件
onSelect={() => onLeaderChange(value)} // 只触发一个回调