re-frame 1.3.0 发布解析:订阅反应上下文告警、enrich 返回 nil 与 reg-sub 语法糖
前端Web框架【免费下载链接】re-frameA ClojureScript framework for building user interfaces, leveraging React项目地址https://gitcode.com/gh_mirrors/re/re-frame点击查看免费下载导读本文围绕 re-frame 1.3.0 版本2022-08-27 定版含 rc1/rc2/rc3 三个候选版本的发布说明展开逐条讲解本次版本的三项核心变更在非反应式上下文reactive context中调用subscribe时给出告警、enrich拦截器允许返回nil以保留原 db、为reg-sub提供:-/:-/:语法糖以及nil作为:fx值由:error降级为:warn日志级别。读完本文你将理解这些变更背后的设计动机掌握对应 API 的正确用法并能从源码层面定位其实现位置。版本背景re-frame 1.3.0 正式发布于 2022-08-27此前经历了 1.3.0-rc32022-03-04、rc22021-12-22与 rc12021-12-20三个候选版本的迭代最终 1.3.0-rc3 被重新打标签re-tag为 1.3.0 正式版。其 Maven 坐标可从 Clojars 页面 获取用于 Maven、tools.deps 与 Leiningen 三种构建方式。版本改动按“Added / Fixed / Changed”分类组织本文依此展开版本时间类型核心内容1.3.0 (final)2022-08-27—1.3.0-rc3 重新打标签为正式版rc32022-03-04Added非反应式上下文调用subscribe时告警enrich允许返回nilrc22021-12-22Fixed修复关于 reg-sub 新增强化的文档rc12021-12-20Added/Changedreg-sub语法糖nil:fx值日志降级为:warn一、非反应式上下文中调用 subscribe 的告警rc3变更内容在 rc3 中re-frame 新增了对“在反应式上下文之外调用subscribe”的场景检测当应用处于 debug 模式且当前并不处于 reagent 渲染函数render function的跟踪上下文中时控制台会打印一条:warn级告警提示订阅可能泄漏。此改动源自 day8/re-frame#740由 dannyfreeman 实现。为什么这是个问题subscribe是有状态的。订阅会创建 reagent reaction 并进入订阅缓存其生命周期依赖 reagent 组件对它的“依赖引用”组件在渲染时调用subscribe会建立引用卸载时移除引用最后一个组件卸载后订阅节点才会被清理。如果在一个没有组件引用的位置调用subscribe就没有任何机制在未来处置dispose这个订阅反复调用会造成内存泄漏。这一点在官方 FAQ 文档 UseASubscriptionInAnEventHandler 中有完整阐述唯一安全的subscribe调用点是 reagent 渲染函数内部。典型的错误场景包括;; 错误DOM 事件回调中调用 subscribereagent/re-frame 无法在此时管理订阅生命周期 (defn my-btn [] [:button {:on-click #(subscribe [:some (gensym)])}]) ;; 错误在事件处理器中调用 subscribe (re-frame.core/reg-event-db :event-id (fn [db v] (let [sub-val (subscribe [:something])] ;; --- 可能的内存泄漏 ....)))re-frame 的事件处理循环运行在 reagent 渲染循环之外的独立上下文中因此事件处理器里的subscribe既无法被正确缓存复用也无法被自动处置。FAQ 中给出的替代方案包括把计算逻辑抽成纯函数在订阅与事件处理器之间共享、在回调中dispatch事件而非订阅、改用re-frame.flow其派生的值可在任何时间任何地点访问或使用re-frame.alpha的自定义生命周期订阅。源码实现告警逻辑位于 src/re_frame/subs.cljc(defn warn-when-not-reactive [] (when (and debug-enabled? (not (reactive?))) (console :warn re-frame: Subscribe was called outside of a reactive context.\n https://day8.github.io/re-frame/FAQs/UseASubscriptionInAnEventHandler/)))reactive?直接委托给 reagent 的reagent.ratom/reactive?见 src/re_frame/interop.cljs它返回当前是否处于 reagent 反应式跟踪环境。warn-when-not-reactive在subscribe的两个 arity 中均被调用src/re_frame/subs.cljc即单参与带动态参数dyn-v的订阅都会先做此检查。需要特别说明的两点仅在 debug 模式下生效debug-enabled?为真时才会告警生产构建advanced 编译不受影响。只告警、不拦截这是一条:warn级提示订阅本身仍会创建。测试 test/re_frame/subs/alpha_test.cljs 通过with-redefs模拟reactive?的不同返回值验证了“已缓存的订阅在非反应式上下文也可用”“reactive生命周期订阅即使脱离组件也会被缓存”等行为——说明在特定场景如订阅已在组件内建立下外部调用并非一定会泄漏。如何正确解决将计算逻辑抽成纯函数订阅与事件处理器共享(defn circle-area [r] (* Math/PI r r)) (def get-areas (comp circle-area :circles)) (reg-sub :areas (fn [db _] (get-areas db))) (reg-event-fx :store-areas (fn [{:keys [db]} _] {:local-store (get-areas db)}))回调中改用dispatch(rf/reg-fx :circle-effect circle-effect!) (re-frame.core/reg-event-fx :clicked-button (fn [{{:keys [circles]} :db :as coeffects} event-v] {:circle-effect circles})) [:input {:type button :value Click me! :on-click #(dispatch [:clicked-button %])}]二、enrich 拦截器允许返回 nilrc3变更内容rc3 放宽了enrich拦截器的约束传给enrich的函数现在可以返回nil此时 re-frame 会保留传入的db作为结果而不是把nil写入 app-db。此改动来自 day8/re-frame#751由 NoahTheDuke 提出。设计与用途enrich是 re-frame 提供的标准拦截器它在:after阶段运行给定的函数ff接收db与event两个参数并返回修改后的db。与只做副作用的after不同enrich期望f对 db 做有意义的加工参与到“派生数据”的流转中。其经典应用是在 todomvc 这类应用中任何 CRUD 事件都可能改变重复项检测的结果若在每个事件处理器里各自实现检测逻辑容易产生微妙的差异而用enrich挂一个“每次全部重算”的纯函数则既保证不错过任何变更又可以独立于 CRUD 事件单独测试这个纯函数。允许返回nil的实际价值在于有时是否应用变更需要在运行时判断而非注册处理器时静态决定。例如(def set-last-update (core/enrich (fn [{db :db} [_ {user :user}]] (when (active-user? user) ;; - 仅当用户处于活跃状态时才更新 ...))))在此之前函数必须保证每个分支都返回 db否则会把nil作为新 db 生效现在只需在不需要修改时返回nilre-frame 会自动回退到传入的 db 值。源码与测试印证实现位于 src/re_frame/std_interceptors.cljc(defn enrich [f] (-interceptor :id :enrich :after (fn enrich-after [context] (let [event (get-coeffect context :event) prev-db (if (contains? (get-effect context) :db) (get-effect context :db) (get-coeffect context :db)) new-db (f prev-db event)] (assoc-effect context :db (or new-db prev-db))))))关键在最后一行(or new-db prev-db)当f返回nil时取prev-db作为新的 db 效果。prev-db的选取也很有讲究——若本次事件已产生:dbeffect则用该新 db 作为f的输入否则回退到 coeffect 中的原始 db。测试 test/re_frame/interceptor_test.cljs 专门覆盖了“f 返回 nil 时使用传入 db”的语义(testing uses given db if f returns nil (let [updater (db-handler-interceptor (fn [db _] (update db :a inc))) ctx (context [] [updater] {:a 1}) enrich-interceptor (enrich (fn [_ _] nil)) result (- ctx ((:before updater)) ((:after enrich-interceptor)))] (is ( {:a 2} (get-effect result :db)))))事件处理器先把{:a 1}更新为{:a 2}随后enrich返回nil最终结果仍为{:a 2}证明 db 未被nil覆盖。完整 API 文档见 src/re_frame/core.cljc其中同样记录了“Iffreturnsnil, thedbvalue passed tofwill be returned instead”的语义。三、reg-sub 语法糖rc1变更内容rc1 引入了“对琐碎 reg-sub 声明trivial reg-sub declarations的语法糖”来自 day8/re-frame#634由 bsboiko 贡献。所谓“琐碎”指的是那些只有一个输入信号、计算函数只需关心信号值而无需关心 query 向量的订阅。语法糖形态在既有:-基础上rc1 让reg-sub的调用形式更加紧凑。官方订阅指南 docs/subscriptions.md 展示了:-的经典用法(reg-sub :id ;; 输入信号 :- [:a] ;; 等价于 (subscribe [:a]) :- [:b 2] ;; 等价于 (subscribe [:b 2]) ;; 计算函数 (fn [[a b] query-v] (calculate-it a b)))在此基础上rc1 补充的语法糖允许省略计算函数中多余的参数。reg-sub的具体解析逻辑在 src/re_frame/subs.cljc 的sugar函数中若第二个到倒数第二个参数为空、或为单个函数、或为query?标识则直接以最后一个参数为计算函数1 参。若倒数第二个参数是:-则把其后的单参函数包装为(fn [db _] (f db))——计算函数只接收信号值忽略 query 向量。若倒数第二个参数是:则包装为(fn [db q] ...)——信号值与 query 向量中的参数都会传入f。:-则与紧随其后的 query 向量组成一对输入信号声明支持多对。实践对比以文档 docs/correcting-a-wrong.md 中的时钟订阅为例原始写法(reg-sub :time-str :- [:time] ;; 这是 signals 函数的语法糖 (fn [t _] (- t .toTimeString (clojure.string/split ) first)))用:-糖可以把计算函数简化为单参形式(reg-sub :time-str :- [:time] :- (fn [t] (- t .toTimeString (clojure.string/split ) first))))在 re-frame 的事件流flow示例 examples/flow/src/re_frame/flow/demo.cljs 以及官方 docs/Flows.md 中也能看到(rf/reg-sub :current-tab :- :tab)、(rf/reg-sub :items :- (comp reverse :items))这类极简写法——当信号值就是整个 db、计算只是 db 的简单映射时甚至可以直接传关键字或函数组合无需显式fn。同时这一糖同样作用于re-frame.alpha的reg :sub见 docs/api-re-frame.alpha-instrumented.md 与 src/re_frame/alpha.cljc 的说明保证新旧订阅 API 的声明体验一致。文档修复rc2rc2 的 Fixed 条目是“修复关于 reg-sub 新增强化的文档”由 eval 等文档中的示例与实际实现保持一致。四、nil 作为 :fx 值日志从 :error 降级为 :warnrc1变更内容rc1 同时修改了事件处理器返回nil作为:fx效果值时的处理方式此前会打印:error级日志现在降为:warn。背景与含义:fx是 re-frame 内建的效果处理器期望值是一个“效果向量”的集合每个元素形如[effect-key effect-value]nil条目会被忽略从而支持条件式地追加效果见 src/re_frame/fx.cljc。因此事件处理器返回{:fx nil}并非致命错误——它等价于“本次不产生任何副作用”。在:fx的 handler 中对非 seq 输入会提示:fx effect expects a seq对无对应 handler 的效果键会提示 no associated handler均为:warn见 src/re_frame/fx.cljc。rc1 将nil值的处理从:error调整为:warn统一了这类“可忽略但不规范”输入的日志等级仍提示开发者注意:fx的期望输入形态但不再把返回nil当作错误对待。这一改动与 rc3 中enrich允许返回nil的精神一脉相承——re-frame 越来越倾向于把“可选/空值”从错误语义中剥离出来只在真正异常时使用:error。五、变更对升级的影响与小结综合 1.3.0 的四个改动对升级者的实践建议如下升级后留意控制台告警若应用在 debug 模式下出现 “Subscribe was called outside of a reactive context” 的:warn说明存在非安全订阅点应按 docs/FAQs/UseASubscriptionInAnEventHandler.md 中的方案重构抽纯函数共享、回调改 dispatch、改用 flow 或 alpha 生命周期订阅。enrich 的新语义让拦截器更易写条件性更新场景如“仅活跃用户才更新”不再需要为每个分支手动返回 db返回nil即可保留原值实现见 src/re_frame/std_interceptors.cljc。reg-sub 声明更简洁单信号 简单映射的订阅可用:-糖一行写出多输入仍使用:-对完整语法解析见 src/re_frame/subs.cljc。:fx返回 nil 不再报错可放心使用条件表达式生成:fx序列(when cond [...] )不必担心nil触发错误日志。相关代码与文档在仓库中的位置汇总订阅告警实现src/re_frame/subs.cljcreactive?委托src/re_frame/interop.cljsenrich拦截器src/re_frame/std_interceptors.cljc、API 文档 src/re_frame/core.cljcenrichnil 语义测试test/re_frame/interceptor_test.cljsreg-sub语法糖解析src/re_frame/subs.cljc:fx效果处理器src/re_frame/fx.cljc订阅相关 FAQdocs/FAQs/UseASubscriptionInAnEventHandler.md订阅语法糖指南docs/subscriptions.md赞分享前端Web框架【免费下载链接】re-frameA ClojureScript framework for building user interfaces, leveraging React项目地址https://gitcode.com/gh_mirrors/re/re-frame点击查看免费下载相关推荐re-frame 订阅外部数据实战指南用 reg-sub-raw 让 app-db 成为唯一的数据入口re frame 订阅外部数据实战指南用 reg sub raw 让 app db 成为唯一的数据入口 re frame 是构建在 React 之上的 C前端Web框架re-frame 中 reg-event-fx 返回 cofx 而非 fx 的陷阱理解 effect 与 coeffect 的本质区别re frame 中 reg event fx 返回 cofx 而非 fx 的陷阱理解 effect 与 coeffect 的本质区别 本文围绕 re fra前端Web框架开源项目的合规边界从PyWxDump事件看技术开发的法治意识开源项目的合规边界从PyWxDump事件看技术开发的法治意识 在开源社区蓬勃发展的今天技术创新与法律合规的平衡成为每个开发者必须面对的现实问题。近期一个曾创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考