← [ BACK_TO_NOTES ]

数据获取共置

数据获取共置让真正消费数据的组件或模块靠近对应数据请求,同时保留 Repository、Service、API Client 等领域与访问层边界。

#concept #growing #architecture #react

[!info] related notes

  • 所属 MOC:
  • 前置概念:
  • 并列概念:
  • 易混淆概念:
  • 关系笔记:

数据获取共置

一句话定义

数据获取共置(Data Fetching Colocation)是指:让数据请求靠近真正消费这份数据的组件或模块,而不是把所有请求机械集中在最顶层 Page;但访问数据库、调用服务和领域规则仍应保留清晰的数据层/服务层抽象。

为什么需要它

传统页面很容易写成:

DashboardPage
├── fetch profile
├── fetch assessments
├── fetch notifications
├── fetch ai insight
│
├── ProfilePanel
├── AssessmentPanel
├── NotificationPanel
└── AIInsightPanel

顶层组件同时负责:

  • 所有数据获取
  • 所有 loading / error 判断
  • 大量 props 下发
  • 所有请求的执行时序

随着页面增大,它会成为数据编排瓶颈。

共置后可以变成:

DashboardPage
├── ProfilePanel
│    └── getProfile()
├── AssessmentPanel
│    └── getAssessments()
├── NotificationPanel
│    └── getNotifications()
└── AIInsightPanel
     └── getAIInsight()

谁真正依赖数据,谁更接近数据请求。

Next.js Server Components 为什么适合共置

Server Component 可以直接 await:

async function AssessmentPanel() {
  const assessments = await getAssessments()
  return <AssessmentList assessments={assessments} />
}

因此数据请求不必全部提升到 Page:

export default function DashboardPage() {
  return (
    <>
      <ProfilePanel />
      <AssessmentPanel />
      <NotificationPanel />
    </>
  )
}

这可以让组件依赖更局部,也更容易配合 Suspense 定义独立等待边界。

共置不等于“在 JSX 里到处写 SQL”

错误理解:

async function GoalCard() {
  const result = await db.$queryRaw`...`
  // 大量 SQL / 事务 / 权限规则
}

如果每个 UI 组件直接操作底层数据源,会造成:

  • SQL/ORM 细节散落
  • 权限逻辑重复
  • 难以测试
  • UI 与 schema 强耦合
  • 领域规则难以复用

更稳的结构是:

Server Component
      ↓
query / data function
      ↓
Repository / API Client
      ↓
Owning Service / DB

例如:

// data/assessments.ts
export async function getAssessmentsForUser(userId: string) {
  return assessmentApi.listForUser(userId)
}

组件只表达它需要什么:

const assessments = await getAssessmentsForUser(userId)

UI 共置与 Domain Ownership 是两条轴

共置回答:

哪个组件最接近这份数据的消费点?

而 数据所有权与服务边界 回答:

谁有资格提供和修改这份数据?

因此:

Fetch near consumer
≠
Bypass owning service

即使 AssessmentPanel 自己发起数据获取,它也可以:

AssessmentPanel
→ assessmentApi.get(...)
→ Go Backend

而不是直接查 Go 服务数据库。

与 Props Drilling 的关系

没有共置时:

Page fetches A
↓
Container receives A
↓
Section receives A
↓
Leaf finally uses A

很多中间组件只是转发数据。

共置可以减少不必要的数据 props 链:

Leaf Server Component
→ own server-side data dependency

但如果多个兄弟组件必须共享同一个一致性快照,或者请求本来就应该在统一聚合层完成,也不必为了“共置”强行拆开。

与 Request Waterfall 的关系

顶层集中请求不一定慢;如果独立请求使用 Promise.all,它也可以很高效。

共置的价值更多在于:

  • 依赖局部化
  • 组件边界清晰
  • 更自然地配合 Suspense
  • 避免一个慢模块阻塞整个页面

见 Request Waterfall 与并行数据获取。

与 Suspense / Streaming 的关系

假设 AI Insight 慢 5 秒:

<ProfilePanel />
<AssessmentPanel />

<Suspense fallback={<AIInsightSkeleton />}>
  <AIInsightPanel />
</Suspense>

AIInsightPanel 自己获取数据,就能形成清晰的:

consumer boundary
+
data boundary
+
loading boundary

这比 Page 先统一等待所有数据,再把结果下发更利于局部 Streaming。

什么时候不应该下沉

多个组件需要完全相同的聚合结果

如果真正的业务对象就是“Dashboard Snapshot”,由后端一次性返回一个一致性视图,那么把它拆成 8 个独立请求可能反而破坏一致性和性能。

数据获取本质属于上层编排

例如:

先取得当前 workspace
→ 根据 workspace policy 决定后续哪些模块可见

上层本来就承担协调职责,此时不应该机械地下沉全部逻辑。

会造成 N+1

如果每个列表 Item 都自己向后端查询关联数据:

100 GoalCard
→ 100 requests

这不是健康的共置,而是请求粒度失控。

共置的真正边界

好的共置通常是:

UI/feature boundary
≈
data consumption boundary

但不是:

每个小组件
=
一个独立 HTTP/SQL 请求

最短记忆

数据靠近消费者,
访问逻辑留在 data/service layer,
业务 authority 留在 owning service。
创建于 2026/9/15 更新于 2026/9/15
Digital Biome
Access

键盘导航

j / k / ↑ / ↓
选择对象
Enter / o
打开选择
/ 或 Ctrl+K
全站搜索
i / f / 1–4
筛选当前列表
g → h / l / p / t / a
首页 / 笔记 / 项目 / 工具 / About
r → 1–9
打开所选对象的关联
u
返回上一页
Esc
关闭浮层 / 取消命令;输入框内清空文本