【免费下载链接】worldwideview
WorldWideView 是一款在浏览器中运行的大型3D 地球实时态势感知平台,它用Zustand 状态管理方案,把相机、图层、时间轴、UI、过滤、数据、配置、收藏、告警等状态拆分成九个(后扩展为十个)独立数据切片(Slice),每个切片一个文件、职责单一,再拼装成一个统一 Store。本指南面向新手,带你理解这套"切片式状态管理"是怎么组织和运转的。
为什么 3D 地球应用需要"切片式"状态管理?
想象一下:屏幕左侧是图层面板,中间是每秒刷新几十次的 3D 地球,右侧是选中卫星的详情卡,底部还有时间轴。这些 UI 全部依赖同一份应用状态——相机在哪、哪些图层开着、当前选中了谁。
如果把所有状态塞进一个大文件,会立刻遇到三个问题:
- 文件爆炸:几十个字段 + 几十个 action 挤在一起,没人看得懂;
- 过度渲染:React 组件一旦订阅整个 Store,任何字段变化都会触发重渲染;
- 难以测试:改一个相机逻辑要通读全文件。
WorldWideView 的解法是经典的Zustand Slice Pattern:每个领域一个切片文件,切片导出一个工厂函数(如createGlobeSlice),最后在 store.ts 中合并成一个 Store。官方架构文档在 docs/ARCHITECTURE.md 的 "State Management" 一节有完整说明。
一站式注册:九个切片如何拼成一个 Store
核心逻辑就在 src/core/state/store.ts 里,思路非常简单:
- 先用 TypeScript 交叉类型定义总类型:
AppStore = GlobeSlice & LayersSlice & TimelineSlice & ...; - 调用
create<AppStore>(),把每个切片工厂的返回值展开合并进去; - 所有组件和工具函数统一通过
useStore这个 Hook 访问状态。
export const useStore = create<AppStore>((...args) => ({ ...createGlobeSlice(...args), ...createLayersSlice(...args), ... }));使用方式只有两种(见 docs/development.md):
- React 组件内:
useStore((s) => s.cameraLat)—— 只选取需要的字段,避免不必要的重渲染; - React 之外(动画循环、插件加载器):
useStore.getState()直接快照读取。
一个关键性能细节:3D 动画循环(requestAnimationFrame驱动)每帧用getState()快照一次,而不是订阅 Store——因为帧循环本身就是节拍器,再加订阅只会造成重复触发。
九大数据切片逐个拆解 🧩
所有切片位于 src/core/state/ 目录,下面是完整清单:
| 切片 | 文件 | 一句话职责 |
|---|---|---|
| 🌍 globe | globeSlice.ts | 3D 相机位置、朝向与 FPS |
| 🗂️ layers | layersSlice.ts | 各插件图层的开关与加载状态 |
| ⏱️ timeline | timelineSlice.ts | 时间窗、回放与历史窗口 |
| 🖥️ ui | uiSlice.ts | 主题、面板开关、选中实体 |
| 🔍 filter | filterSlice.ts | 按图层的过滤条件 |
| 📦 data | dataSlice.ts | 实体数据总缓存 |
| ⚙️ config | configSlice.ts | 数据配置与地图渲染参数 |
| ⭐ favorites | favoritesSlice.ts | 用户收藏的实体 |
| 🔔 alerts | alertsSlice.ts | 告警规则、Toast 与未读角标 |
| 📡 seederHealth | seederHealthSlice.ts | 数据源健康度遥测(后加入的第 10 个) |
🌍 globe 切片:相机即状态
globeSlice记录相机的经纬度、高度、航向、俯仰、翻滚六个参数,外加isAnimating(是否正在飞行动画)和实时fps。默认视角是全球俯视:cameraLat: 20, cameraLon: 0, cameraAlt: 20000000(约 2 万公里高空)。任何"飞往某地"的操作本质就是调用setCameraPosition()更新这几个字段。
🗂️ layers 切片:图层的生命周期
layers是一个pluginId → LayerState的映射,每个图层记录四件事:enabled(是否开启)、entityCount(加载了多少实体)、loading(是否在等待网络)、fetchedAt(数据新鲜度)。此外还有预算裁剪结果(budgetCap/renderedCount)——当某个数据源一次性推送过多实体时,系统会按预算做抽稀,并如实把"被裁剪"这件事暴露到 UI,而不是悄悄丢弃。
⏱️ timeline 切片:时间的遥控器
时间切片管理currentTime、时间窗(1h / 6h / 24h / 48h / 7d)和回放控制(isPlaying、playbackSpeed、isPlaybackMode)。切换时间窗时会自动重算绝对时间范围timeRange,历史数据可用性按插件分别记录在timelineAvailability中。
🖥️ ui 切片:界面的一切"开关"
这是字段最多的切片:五套主题(dark / light / legacy / black / tactical)循环切换、左右侧栏与配置/过滤面板的开合、selectedEntity(当前选中实体)、hoveredEntity(鼠标悬停实体)、浮动视频流窗口(floatingStreams)、全局错误 Toast 等。主题选择还会同步写入localStorage和根元素的data-theme属性。
🔍 filter 切片:按图层存过滤条件
结构是一个两层嵌套映射[pluginId][filterId] → FilterValue,动作只有三个:setFilter、clearFilters(清空某插件)、clearAllFilters(全部清空)。真正的过滤计算发生在 src/core/filters/filterEngine.ts,切片只负责"记住你勾选了什么"。
📦 data 切片:实体数据的总缓存
entitiesByPlugin按插件存放GeoEntity[]数组,是整个数据管线的落点。一个精妙的设计是:setEntities更新数据时,如果发现当前选中的实体属于该插件,会自动用最新数据刷新selectedEntity——保证右侧详情卡上的坐标永远是新鲜的,组件不需要额外同步逻辑。
⚙️ config 切片:双配置区
dataConfig管数据面:每个插件的轮询间隔、缓存开关与过期时间、并发请求数、重试次数;mapConfig管渲染面:分辨率缩放、抗锯齿模式(none/fxaa/msaa)、阴影、光照、底图 ID、2D/2.5D/3D 场景模式。修改底图时会顺手持久化到localStorage。
⭐ favorites 切片:收藏与跨端持久化
收藏操作按运行环境分叉:演示模式写 Cookie(一年有效期),本地/云模式则POST /api/user/favorites落库。去重逻辑内置——同一实体不会重复收藏。
🔔 alerts 切片:规则 + 提醒
持有告警规则列表(通过/api/alerts做 CRUD)、未读角标计数和最多 4 条的 Toast 队列。数据总线触发alertFired事件后,这个切片负责把它们变成屏幕上的提醒。
📡 补充:seederHealth 切片
这是后加入的第 10 个切片,记录每个数据播种器的健康度(最后运行时间、失败次数、是否过期)。它同时接收 HTTP 快照(首次填充)和 WebSocket 增量(实时覆盖),让"数据源是否还活着"直接显示在图层列表的徽章上。
数据流全景:从 WebSocket 推送到一个图标 🚀
状态切片不是孤立存在的,它处在一条严格的单向数据管道末端:
引擎推送 /stream → WsClient 路由 → DataBus.emit("websocketData", ...) → _hydrateSnapshot() → Store.entitiesByPlugin(data 切片) → GlobeView(memo 化的可见实体)→ EntityRenderer(3D 图元) → AnimationLoop(视野裁剪、悬停/选中)高频原始事件走的是 DataBus 这个类型化发布/订阅总线,刻意绕开 React 渲染周期;只有需要驱动 UI 的状态才批量写入 Zustand Store。这条设计让"每秒上千次数据更新"和"React 组件树"互不拖累。
切片模式的可测性红利 🧪
每个切片都是纯函数工厂(接收set/get),天然适合单元测试。查看 StateSlices.test.ts 就能看到写法:用 mock 的set捕获状态变更,然后断言——
slice.setCameraPosition(10, 20, 30)后,cameraLat是否等于 10;slice.toggleLayer("p1")后,图层enabled是否翻转;slice.setEntities(...)后,选中的实体是否同步刷新。
每个切片都配有独立的*.test.ts(如 globeSlice.test.ts、filterSlice.test.ts),状态层的回归基本靠它们兜底,测试策略详见 docs/testing.md。
新手速查表 📋
| 想知道… | 去哪里看 |
|---|---|
| Store 如何组装 | src/core/state/store.ts |
| 某个领域的状态字段 | src/core/state/ 对应切片文件 |
| 切片划分的设计理由 | docs/ARCHITECTURE.md "State Management" 一节 |
| 订阅的性能规范 | docs/development.md |
| 切片测试写法 | StateSlices.test.ts |
给初学者的三条建议:
- 只选需要的字段——
useStore((s) => s.theme)优于取整个 Store,这是切片模式发挥性能优势的前提; - 新增状态先找对切片——相机相关进 globe,图层生命周期进 layers,别随手加到 ui 切片里;
- 副作用放切片内、计算放外部——切片里
set()更新状态,复杂推导交给filterEngine这类独立模块。
看完这一篇,你再打开 WorldWideView 的源码目录,src/core/state/ 下十个文件对应的就是这十个"器官":相机、图层、时间、界面、过滤、数据、配置、收藏、告警、健康度——一个完整的 3D 地球应用,就是由这九(十)个数据切片共同驱动起来的。
【免费下载链接】worldwideview
相关推荐
Zustand多Store管理:大型React应用的状态组织终极指南 🚀
Zustand多Store管理:大型React应用的状态组织终极指南 🚀 想要构建大型React应用却为状态管理头疼?Zustand的多Store管理模式正是
前端Bilibili-Evolved状态分片管理:大型应用的状态组织
Bilibili Evolved状态分片管理:大型应用的状态组织 在大型Web应用开发中,状态管理往往是架构设计的核心挑战。Bilibili Evolved作为
前端音视频终极指南:如何用Zustand切片模式构建大型React应用状态架构
在当今复杂的前端应用开发中, 状态管理 是每个开发者必须面对的核心挑战。随着应用规模的不断扩大,传统的单一状态存储往往变得臃肿不堪,维护成本急剧上升。Zusta
前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考