尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
re-frame 应用状态管理:深入理解 app-db 单一数据源架构
前端Web框架【免费下载链接】re-frameA ClojureScript framework for building user interfaces, leveraging React项目地址https://gitcode.com/gh_mirrors/re/re-frame点击查看免费下载导读本文以 re-frame 官方文档《Application State》为主体系统讲解 re-frame 如何将整个应用的全部状态收敛进一个名为app-db的 Reagent atom内存数据库中。你将理解为什么数据在休息时是近乎完美的、把状态集中到一处能带来哪些架构级收益并掌握app-db的读写通道订阅与事件处理、schema 校验、状态检查手段以及开发热重载时的订阅缓存陷阱从而在实战中构建真正可解释、可调试、可回放的单向数据流应用。一、The Big Ratom一切状态汇于一处re-frame 把整个应用的所有状态放进一个地方这个地方叫app-db。对真正的数据库而言把格式良好的数据统一放进 PostgreSQL这一点几乎毫无争议。但在一个运行中的、内存态的应用内部很多人会犹豫如果你有面向对象OO背景这种数据集中在一处的做法会非常难以接受——你习惯了把系统拆成许多小块按行为组织、把状态隐藏起来。正如 Fogus 所言Well-formed Data at rest is as close to perfection in programming as it gets. All the crap that had to happen to put it there however...格式良好的静止数据几乎就是编程中离完美最近的东西而为了让数据变成那样所发生的一切糟心事……则另说。在 re-frame 中app-db是这样一个东西(def app-db (reagent/atom {})) ;; a Reagent atom, containing a map虽然它本质上是一个Reagent atom简称ratom但官方文档强烈建议你把它当作一个内存数据库in-memory database来看待而不是atom 里的一个 map。理由很实际app-db里装着的是结构化数据你需要查询它Query、要对它做 CRUD 和各种变换Transform、常常还要原子地事务化地更新它Transact——这些操作和对待一个真正的数据库完全同构因此内存数据库这个心智模型远比一个 atom 里的 map更有用。源码佐证app-db 的真实定义仓库中app-db的实际定义在 src/re_frame/db.cljc(ns re-frame.db (:require [re-frame.interop :refer [ratom]])) ;; -- Application State -------------------------------------------------------------------------- ;; ;; Should not be accessed directly by application code. ;; Read access goes through subscriptions. ;; Updates via event handlers. (def app-db (ratom {}))注意两个细节ratom并不是reagent.core/atom的直接别名而是通过 src/re_frame/interop.cljs 封装的(defn ratom [x] (reagent.core/atom x))。interop是 re-frame 与 Reagent/宿主环境之间的适配层将来替换底层响应式实现时只需改这一处。文件顶部的注释明确划定了app-db的访问纪律应用代码不应直接访问它读取走 subscriptions更新走 event handlers。这正是 re-frame 单向数据流的核心约束。几个需要记住的命名与事实为什么叫app-db而不是app-state作者刻意选择app-db这个名字就是为了尽可能强烈地传达内存数据库这一概念而不是普通的状态变量。app-db与db是两回事在文档和代码里作者严格区分app-db那个ratom容器本身与db当前存放在这个 ratom 内部的值即一个 map。读代码时务必注意这一命名约定——例如事件处理函数的第一个参数习惯命名为db指的就是当前 app-db 里的值。你不需要自己声明app-dbre-frame 会自动创建并管理它上面的db.cljc就是唯一创建点想查看它当前保存的值参见 检查 app-db 的 FAQ。app-db不一定要是装着 map 的 ratom它也可以是 Datascript 数据库之类的东西。实际上任何能在变化时发出信号的数据库都可以胜任。re-frame 作者坦言非常想用 Datascript——那么酷——只是他们的应用数据量太大。如果改用 Datascript需要稍微调整 re-frame并配合 re-posh 之类的桥接方案。二、为什么数据集中是好主意六大架构收益把数据放在一处能带来如下收益这也是 re-frame 架构最核心的设计哲学1. 单一事实来源消灭状态同步代码最关键的一条因为只有一个真相源single source of truth我们完全不需要写代码去同步多个有状态组件之间的状态。作者强调怎么强调都不过分——你会写出更少的代码并且整整一类 bug因状态不同步而产生的 bug被直接消除。这与 OO 范式截然相反OO 把状态分散到各个对象里然后不得不时刻小心翼翼地保持它们同步、同时还要隐藏它们——作者直言自己多年这么做现在想想其实挺疯狂的。2. 一次reset!完成事务性提交因为全部状态合并进了同一个 atom一次reset!就能完成整体更新其行为等同于一次事务性提交应用在某一瞬间从状态 A 变成状态 B绝不会经历一系列渐进步骤、从而停留在某个暂时不一致的中间态。这种简单性同样让一整类 bug 或设计难题凭空消失。3. 全量数据随时可校验app-db中的数据可以被赋予强 schema于是任何时刻都可以对应用中的全部数据做校验——每运行完一个事件处理器事件处理器负责计算新状态就校验一次。这让我们能尽早、且精准地发现错误。它的信心增强效果与类型系统类似但文档特别指出一个好的 schema 能提供的杠杆leverage甚至可能超过类型。4. Undo/Redo 变得直截了当因为只有一个中心值快照与恢复都很容易。不可变数据结构的**结构共享structural sharing**特性意味着保留最近 200 个快照也几乎不费多少内存非常高效。对某些应用类别比如绘图应用这近乎魔法原本困难、易出错、破坏性强的 undo/redo 变得微不足道。但文档同时给出了清醒的边界很多 Web 应用并非数据自足它们的数据主要由权威的远程数据库主导。对这类应用app-db更多只是一个本地缓存点对它做 undo/redo 意义有限——因为权威数据源在别处。5. 真正地以状态机FSM建模控制流数据集中为用有限状态机Finite State Machines建模应用控制流提供了基础re-frame 的 Flows 机制即与此相关见 Flows 文档 与 flow-mechanics。6. 时间旅行调试成为可能集中的状态让时间旅行调试time travel debugging成为可能甚至在生产环境也可以做到。三、从atom 里的 map到内存数据库读写通道的正式化把app-db当数据库用关键是要走正式的读写通道。re-frame 通过读走订阅、写走事件把这两条通道在源码层面固化下来。读订阅从 app-db 取数app-db是整个Signal Graph信号图的根节点——一个以app-db为根、以视图函数View Functions为叶子节点的有向无环图DAG。数据从app-db出发被各层订阅节点reg-sub注册的处理函数逐层变换最终以物化视图的形式流入视图层。关于这一数据流拓扑的完整图解见 subscriptions 教程。源码层面在 src/re_frame/subs.cljc 中subscribe直接以app-db为参数调用订阅处理函数(cache-and-return query [] (handler-fn app-db query))带动态参数的重载同样如此subs.cljc(let [dyn-vals (make-reaction (fn [] (mapv deref dynv))) sub (make-reaction (fn [] (handler-fn app-db query dyn-vals)))] ...)也就是说任何订阅最终都是从app-db这个根节点取数。测试 test/re_frame/subs_test.cljs 也验证了这一点reset! db/app-db 1之后订阅值同步变为 1且db/app-db与test-sub始终相等。写事件处理器产出:db效果统一提交写入同样不允许直接reset!。两条路径都经由内置机制coefficient 注入内置:db这个 cofxcoefficient 处理器会把app-db的当前值注入事件处理器的 coeffects见 src/re_frame/cofx.cljc(reg-cofx :db (fn db-coeffects-handler [coeffects] (assoc coeffects :db app-db))):dbeffect 执行事件处理器计算返回的 effects 图中的:db键由内置 effect 处理器统一reset!回app-db见 src/re_frame/fx.cljc(reg-fx :db (fn [value] (if-not (identical? app-db value) (reset! app-db value) (trace/with-trace {:op-type :reagent/quiescent}))))注意其中的identical?优化如果新值与当前值是同一个对象直接跳过reset!——这正是不可变数据结构配合结构共享在性能上的体现也与第 2 条收益一次 reset 即一次原子提交相呼应。:dbeffect 还有一条特殊地位它总是先于其他所有 effect 被执行v1.1.0 起得到保证见 src/re_frame/fx.cljc 与 内置 effects 文档。典型用法api-builtin-effects.md(reg-event-fx :token (fn [{:keys [db]} event] {:db (assoc db :some-key some-val)})) ;; -- new value computed进阶用path拦截器只操作 app-db 的局部当事件只想修改app-db的某个子树时标准拦截器path会在:before阶段把 coeffect 里的:db换成子路径的值、在:after阶段再assoc-in回原处见 src/re_frame/std_interceptors.cljc。这正是事务化更新局部、整体一次生效的落地手段也再次印证了把app-db当作可寻址数据库的思维。四、为数据建模给 app-db 编写 spec既然所有数据都集中在一处你自然会想为它提供一份 spec并收获它带来的杠杆。当然这意味着你需要学习 Clojure 的 spec关于 spec 的基础可查阅 clojure.org 的 spec 文档并且学习是有成本的——所以在最初的实验阶段你或许可以暂时不写 spec 蒙混过关。但不会太久。文档作者用近乎恳求的语气强调答应我你会写一份 spec。答应我。好成交。关于 spec 的价值文档给出两个值得了解的判断spec 潜在的杠杆比类型更大——这是一个大而有趣、但尚未足够主流的观点其增强信心的方式与类型系统类似但一个好的 schema 可能提供比类型更多的杠杆。在实践中这意味着由于app-db是唯一真相源一份覆盖全量的 spec 就能充当运行时的全库约束配合第 3 条收益每个事件处理器运行后都校验实现错误在产生的那一刻就被抓住。五、实战如何检查 app-db 的内容在开发、调试时如何查看app-db里到底装了什么仓库 检查 app-db 的 FAQ 给出了从简到繁的完整答案快捷方法在 REPL 中直接检查re-frame.db/app-db。在 JS 控制台如 figwheel 场景window.re_frame.db.app_db.state。想要可视化浏览 app-db、查看其子路径、对比变更可使用 re-frame-10x 这样的开发工具。强烈建议安装 cljs-devtools它能让控制台输出可展开、可读性极佳的数据结构。更好的方法直接在界面里观察局部状态官方 FAQ 指出你很少需要查看整个app-db在 REPL 里翻看往往也笨拙。更好的做法是在 GUI 界面里直接观察你想看的那部分。来自 escherize 的实用技巧在视图的 hiccup 里加一行[:pre (with-out-str (pprint interesting))]其中interesting是你想观察的值ratom 或订阅注意前面的。pprint输出易读但不紧凑想要更紧凑可以用[:pre (pr-str some-atom)] ;; NB: using pr-str instead of pprint若用pprint需要在view.cljs的 ns 中 require 它[cljs.pprint :refer [pprint]]把短答案和长答案组合起来甚至可以一网打尽[:pre (with-out-str (pprint re-frame.db/app-db))] ;; see everything!或者只看一部分[:pre (with-out-str (pprint (:part re-frame.db/app-db)))] ;; see a part of it!其他检查工具re-Frisk导航并检查 re-frame 数据结构的不错方案。json-html 库yogthos展示效果更炫代价是占用更多屏幕空间且需要引入专门的 CSS。六、开发与测试中的坑为什么要清空订阅缓存围绕app-db的开发还有一个高频陷阱与订阅缓存有关见 Why-Clear-Sub-Cache FAQ。完整因果链如下React 渲染中抛出异常时不会优雅处理从根开始销毁所有组件且不会调用标准的生命周期方法如ComponentWillUnmount。Reagent 依赖无人观看来触发 Reaction 的on-dispose而这部分簿记依赖ComponentWillUnmount。re-frame 的订阅是 Reaction并且为了效率会去重多处视图请求同一订阅时共享一个。创建订阅时re-frame 把从订阅缓存移除自己设为该订阅的on-dispose——当它不再被任何视图观看时即可被释放。一旦你今年唯一的那个 bug让渲染代码抛异常上述清理链全部失效缓存中残留着带 bug 的旧订阅。修复 bug 后 Figwheel 热重载会从根重新渲染视图再次请求订阅——但 re-frame 发现缓存里还有旧订阅直接返回旧的、带 bug 的那个而不是基于修复后的代码重建。于是你面对一个空白页面唯一的出路是整页刷新。解法re-frame 0.9.0 起提供re-frame.core/clear-subscription-cache!——它会为缓存中每个订阅执行on-dispose清空缓存从而在重载后强制重建订阅。该函数在 src/re_frame/core.cljc 中的文档明确写道它可用于开发期与测试期适合在热重载含订阅处理器的命名空间时调用也适合在 React/渲染异常之后调用因为此时组件的清理不完整、其内订阅也没有被正确清理调用该函数可强制解决。这也是 re-frame 官方模板中 figwheel 重载前调用它的原因。总结app-db是 re-frame 一切设计的基石一个由 re-frame 自动创建、官方约束读走订阅、写走事件的 Reagent atom。把它当作内存数据库而非atom 里的 map你就抓住了 re-frame 架构的灵魂——单一事实来源消灭状态同步代码、一次 reset 即事务提交、全量 schema 随时可校验、undo/redo 与时间旅行调试水到渠成。配合 spec 建模、订阅缓存管理clear-subscription-cache!与 FAQ 中的检查技巧你可以在实战中把 re-frame 的单向数据流优势发挥到极致。想继续深入可依次阅读 subscriptions 教程、内置 effects 文档 以及 Interceptors 文档。赞分享前端Web框架【免费下载链接】re-frameA ClojureScript framework for building user interfaces, leveraging React项目地址https://gitcode.com/gh_mirrors/re/re-frame点击查看免费下载相关推荐Re-frame项目应用状态调试指南如何高效检查app-dbRe frame项目应用状态调试指南如何高效检查app db 引言为什么app db调试如此重要 在Re frame架构中 app db 是整个应用状态前端Web框架re-frame 应用状态检查指南从 REPL 到 GUI 的 app-db 探查方案re frame 应用状态检查指南从 REPL 到 GUI 的 app db 探查方案 导读 app db 是 re frame 应用中全部应用状态的唯一存放前端Web框架re-frame项目解析应用状态管理的最佳实践re frame项目解析应用状态管理的最佳实践 概述为什么需要re frame的状态管理方案 在现代前端开发中状态管理一直是复杂应用开发的核心挑战。随着前端Web框架创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

基于深度学习的NCF视频推荐:源码拆解与工程化实践

基于深度学习的NCF视频推荐:源码拆解与工程化实践

简介:基于深度学习神经网络协同过滤模型(NCF)的视频推荐系统源码,面向推荐系统学习者和深度学习实践者,便于理解如何用神经网络建模用户与视频之间的交互关系。资源完整覆盖从数据读取、特征构造、模型构建到训练评估的…

📅 2026/10/13 0:56:38
基于改进Unet的高分辨率城市建筑物遥感变化检测毕设方案

基于改进Unet的高分辨率城市建筑物遥感变化检测毕设方案

简介:这份资源面向计算机、遥感与人工智能方向的本科毕业生及需要完成变化检测课题的学习者,提供一套基于改进U-Net的高分辨率城市建筑物遥感变化检测Python源码与配套说明文档。项目在开源方案基础上进行改进,涵盖模型与编码器选择、嵌入SE模…

📅 2026/10/13 0:56:38
MATLAB异构特征ERM建模:工业/医疗/金融多源数据统一分类

MATLAB异构特征ERM建模:工业/医疗/金融多源数据统一分类

简介:本资源是一份面向数据科学家、算法工程师及高校研究人员的MATLAB机器学习实践项目,聚焦经验风险最小化(ERM)框架下的多特征分类预测建模,适用于智能制造、金融风控与医疗健康等领域的高维异构数据智能分析场景。压…

📅 2026/10/13 0:56:38
MORE NEWS

更多资讯

📰

双轨制分销系统实战:PHP+Vue轻量级直销业务验证包

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Unicorn CPU模拟引擎入门:原理、上手与常见误区

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

51单片机入门:从点灯程序详解GPIO、限流电阻与烧录调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

伯努利试验与直线随机游动:从公式推导到模拟实战

如果让我从概率论里选一个最被低估的知识点,伯努利试验和直线上的随机游动绝对排在我心里的前三。第一次学的时候,它看起来不过是把抛硬币写成了公式,能算的只有“几次成功”“第几步走到哪”。后来才发现,这一节是整个随机过程的…

📰

总体与样本、点估计与区间估计:统计推断底层逻辑全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

DevSpace 命令详解:`devspace status sync` —— 查看 Kubernetes 文件同步状态与同步日志

云原生开发工具DevOps容器编排 【免费下载链接】devspace DevSpace - The Fastest Developer Tool for Kubernetes ⚡ Automate your deployment workflow with DevSpace and develop software directly inside Kubernetes. 项目地址: https://gitcode.com/gh_mirro…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬