状态集中以后,VideoGaga 的工作流可以在多个步骤间共享数据,但“内存里有一份状态”并不等于“用户下次回来还能继续”。刷新页面、切换项目、浏览器限制存储,都会重新暴露数据生命周期问题。
从表单状态扩展到完整项目状态
早期 store 只保存故事、参数和步骤进度。随着创作链路继续完善,场景、角色、道具和分镜也需要跨页面恢复。
提交 51858b8 将项目状态扩展为:
type ProjectState = {
projectId?: string;
storySetting: StorySettingState;
params: WorkflowParamsState;
assets: WorkflowAssetsState;
storyboards: WorkflowStoryboardsState;
progress: Record<number, "done" | "skipped">;
};
这次没有把所有临时 UI 状态都塞进缓存,而是围绕“恢复一个项目需要什么”来选择字段。资产列表、生成结果、上传状态和分镜进入项目状态;纯视觉交互仍留在组件内部。
持久化层也增加了安全包装。直接访问 localStorage 在服务端渲染、隐私模式或存储被禁用时可能抛错,因此 safeLocalStorage 对读取、写入和删除分别做了环境判断与异常兜底。缓存不可用时,页面仍能以无持久化模式运行。
数据缓存与媒体缓存不是同一件事
首页背景视频是另一类问题。它不属于项目状态,文件较大,重复下载会直接影响再次访问的体验。
提交 bf574b4 为首页视频增加 Service Worker:
- 安装阶段尝试预热固定视频资源;
- 请求阶段优先读取 Cache Storage;
- 未命中时请求网络并写入缓存;
- 激活新版本时清理旧版本缓存。
数据状态使用 Zustand persist,静态媒体使用 Service Worker。两者都叫“缓存”,但失效条件、存储位置和失败策略完全不同。
缓存首先是一组边界
这次调整让我更明确地把缓存看成四个问题:
- 哪些数据值得恢复;
- 数据以什么维度隔离;
- 什么时候失效;
- 缓存不可用时系统是否还能工作。
如果只追求“刷新后还在”,很容易把临时状态、旧接口数据甚至错误结果长期留下。缓存的价值不是存得越多,而是在不破坏正确性的前提下减少重复工作。