Redux状态管理的顶级控制(ClojureScript教程)

本文概述

  • 对于Clojure的新手
  • ClojureScript教程:输入Redux
  • ClojureScript教程:最终结果
  • 使用Redux/Re-frame解耦组件意味着干净的状态管理
欢迎回来阅读《出土的ClojureScript》第二期激动人心的部分!在这篇文章中, 我将介绍使用ClojureScript认真对待的下一个重要步骤:状态管理-在这种情况下, 使用React。
使用前端软件, 状态管理非常重要。开箱即用, 有两种方法可以在React中处理状态:
  • 将状态保持在顶层, 并将其(或特定状态的处理程序)传递给子组件。
  • 将纯净的东西扔出窗口, 并具有全局变量或某种Lovecraftian形式的依赖项注入。
一般来说, 这些都不是很好。将状态保持在最顶层很简单, 但是将应用程序状态传递给需要它的每个组件都需要大量开销。
相比之下, 拥有全局变量(或状态的其他原始版本)会导致难以跟踪的并发问题, 从而导致组件在你期望的时候不会更新, 反之亦然。
那么如何解决呢?对于那些熟悉React的人来说, 你可能已经尝试过Redux, 这是JavaScript应用程序的状态容器。你可能出于自己的意愿而发现了这个问题, 大胆地寻找可维护状态的可管理系统。或者, 你可能在阅读JavaScript和其他Web工具时偶然发现了它。
无论人们最终如何看待Redux, 根据我的经验, 他们通常都会想到两个想法:
  • “ 我觉得我必须使用它, 因为每个人都说我必须使用它。”
  • “ 我真的不完全理解为什么这样做会更好。”
一般来说, Redux提供了一种抽象, 使状态管理适合于React的反应性。通过将所有状态状态转移到Redux之类的系统中, 可以保留React的纯度。这样一来, 你头疼的事情就会少很多, 而且总的来说, 更容易推理。
对于Clojure的新手 尽管这可能无法帮助你从头开始全面学习ClojureScript, 但在这里, 我至少将在Clojure [Script]中概述一些基本的状态概念。如果你已经是经验丰富的Clojurian, 请随时跳过这些部分!
回忆一下也适用于ClojureScript的Clojure基础之一:默认情况下, 数据是不可变的。这对于开发非常有用, 并且可以确保你在时间步长N创建的内容在时间步长> N时仍然相同。ClojureScript还通过原子概念为我们提供了一种方便的方式, 让我们在需要时具有可变状态。
ClojureScript中的原子与Java中的AtomicReference非常相似:它提供了一个新对象, 该对象使用并发保证来锁定其内容。就像在Java中一样, 你可以在该对象中放置任何你喜欢的东西-从那时起, 该原子将成为你想要的任何内容的原子引用。
一旦有了原子, 就可以通过使用reset原子设置一个新值!函数(请注意函数中的!–在Clojure语言中, 通常用于表示操作是有状态的或不纯的)。
另请注意, 与Java不同, Clojure并不关心你在原子中添加的内容。它可以是字符串, 列表或对象。动态输入, 宝贝!
(def my-mutable-map (atom {})) ; recall that {} means an empty map in Clojure(println @my-mutable-map) ; You 'dereference' an atom using @ ; -> this prints {}(reset! my-mutable-map {:hello "there"}) ; atomically set the atom (reset! my-mutable-map "hello, there!"); don't forget Clojure is dynamic :)

试剂用自己的原子扩展了这个原子的概念。 (如果你不熟悉Reagent, 请先阅读这篇文章。)此行为与ClojureScript原子相同, 除了它还会触发Reagent中的渲染事件, 就像React的内置状态存储一样。
一个例子:
(ns example (:require [reagent.core :refer [atom]])) ; in this module, atom now refers ; to reagent's atom.(def my-atom (atom "world!"))(defn component [] [:div [:span "Hello, " @my-atom] [:input {:type "button" :value "Press Me!" :on-click #(reset! My-atom "there!")}]])

这将显示一个包含< span> 的单个< div> , 上面写着” 你好, 世界!” 和一个按钮, 正如你所期望的那样。按下该按钮将自动使my-atom突变为包含” 那里!” 。这将触发该组件的重绘, 结果跨度显示为” Hello, there!” 。代替。
Redux状态管理的顶级控制(ClojureScript教程)

文章图片
对于局部的, 组件级的突变来说, 这似乎很简单, 但是如果我们有一个具有多个抽象层的更复杂的应用程序, 该怎么办?还是如果我们需要在多个子组件及其子组件之间共享公共状态?
一个更复杂的例子
让我们用一个例子来探讨一下。在这里, 我们将实现一个粗糙的登录页面:
(ns unearthing-clojurescript.login (:require [reagent.core :as reagent :refer [atom]])); ; -- STATE --(def username (atom nil)) (def password (atom nil)); ; -- VIEW --(defn component [on-login] [:div [:b "Username"] [:input {:type "text" :value @username :on-change #(reset! username (-> % .-target .-value))}] [:b "Password"] [:input {:type "password" :value @password :on-change #(reset! password (-> % .-target .-value))}] [:input {:type "button" :value "Login!" :on-click #(on-login @username @password)}]])

然后, 我们将在主app.cljs中托管此登录组件, 如下所示:
(ns unearthing-clojurescript.app (:require [unearthing-clojurescript.login :as login])); ; -- STATE(def token (atom nil)); ; -- LOGIC --(defn- do-login-io [username password] (let [t (complicated-io-login-operation username password)] (reset! token t))); ; -- VIEW --(defn component [] [:div [login/component do-login-io]])

因此, 预期的工作流程为:
  1. 我们等待用户输入用户名和密码, 然后点击提交。
  2. 这将触发父组件中的do-login-io函数。
  3. do-login-io函数执行一些I / O操作(例如, 在服务器上登录和检索令牌)。
如果此操作被阻止, 则说明我们的应用程序已冻结, 我们已经陷入了麻烦;如果没有冻结, 那么我们就不必担心了!
另外, 现在我们需要将此令牌提供给所有想要对服务器进行查询的子组件。代码重构变得更加困难!
最后, 我们的组件现在不再纯粹是反应性的, 它现在正在管理应用程序其余部分的状态, 触发I / O, 并且通常有点麻烦。
ClojureScript教程:输入Redux Redux是使你所有基于状态的梦想成真的魔杖。正确实施后, 它提供了安全, 快速且易于使用的状态共享抽象。
Redux的内部工作原理(及其背后的理论)在本文范围之外。相反, 我将深入研究ClojureScript的工作示例, 希望该示例可以展示其功能!
在我们的上下文中, Redux由许多可用的ClojureScript库之一实现。这个叫重新架。它在Redux周围提供了Clojure修饰的包装器(我认为), 使它使用起来绝对令人愉悦。
基础
Redux提升了你的应用程序状态, 使组件轻巧。 Reduxified组件只需要考虑:
  • 看起来像什么
  • 它消耗什么数据
  • 它触发什么事件
其余的则在后台处理。
为了强调这一点, 让我们重新在上方重新登录我们的登录页面。
数据库
首先, 我们需要确定应用程序模型的外观。我们通过定义数据的形状来做到这一点, 这些数据可以在整个应用程序中访问。
一个好的经验法则是, 如果数据需要在多个Redux组件之间使用, 或者需要长期保存(就像我们的令牌一样), 那么应该将其存储在数据库中。相反, 如果数据是组件本地的(例如我们的用户名和密码字段), 则它应作为本地组件状态存在, 而不是存储在数据库中。
让我们创建数据库样板并指定令牌:
(ns unearthing-clojurescript.state.db (:require [cljs.spec.alpha :as s] [re-frame.core :as re-frame]))(s/def ::token string?) (s/def ::db (s/keys :opt-un [::token]))(def default-db {:token nil})

这里有一些有趣的要点:
  • 我们使用Clojure的规格库来描述数据的外观。这对于像Clojure [Script]这样的动态语言尤其适用。
  • 在此示例中, 我们仅跟踪一个全局令牌, 该令牌将在用户登录后代表我们的用户。此令牌是一个简单的字符串。
  • 但是, 在用户登录之前, 我们将没有令牌。这由:opt-un关键字表示, 它表示” 可选, 不合格” 。 (在Clojure中, 常规关键字可能类似于:cat, 而合格关键字可能类似于:animal / cat。合格通常发生在模块级别–这阻止了不同模块中的关键字相互破坏。)
  • 最后, 我们指定数据库的默认状态, 即默认状态。
在任何时候, 我们都应该确信数据库中的数据与此处的规范相符。
订阅内容
既然我们已经描述了我们的数据模型, 那么我们需要反映一下我们的视图如何显示该数据。我们已经描述了视图在Redux组件中的样子-现在我们只需要将视图连接到数据库即可。
使用Redux, 我们不会直接访问数据库-这可能会导致生命周期和并发问题。相反, 我们通过订阅将我们的关系注册到数据库的某个方面。
订阅告诉重新框架(和Reagent)我们依赖数据库的一部分, 如果该部分被更改, 则应该重新渲染我们的Redux组件。
订阅很容易定义:
(ns unearthing-clojurescript.state.subs (:require [re-frame.core :refer [reg-sub]]))(reg-sub :token; < - the name of the subscription (fn [{:keys [token] :as db} _] ; first argument is the database, second argument is any token)); args passed to the subscribe function (not used here)

在这里, 我们为令牌本身注册了一个订阅。预订只是预订的名称, 是从数据库中提取该项目的函数。我们可以为该值做任何我们想做的事情, 并尽可能多地改变视图。但是, 在这种情况下, 我们只是从数据库中提取令牌并返回它。
订阅可以做很多工作, 例如在数据库的各个子部分上定义视图, 以缩小重新渲染的范围, 但现在我们将使其保持简单!
大事记
我们有我们的数据库, 也有我们对数据库的看法。现在我们需要触发一些事件!在此示例中, 我们有两种事件:
  • 将新令牌写入数据库的纯事件(无副作用)。
  • 通过一些客户端交互出去并请求我们的令牌的I / O事件(具有副作用)。
我们将从简单的一开始。重新构架甚至提供了针对此类事件的功能:
(ns unearthing-clojurescript.state.events (:require [re-frame.core :refer [reg-event-db reg-event-fx reg-fx] :as rf] [unearthing-clojurescript.state.db :refer [default-db]])); our start up event that initialises the database. ; we'll trigger this in our core.cljs (reg-event-db :initialise-db (fn [_ _] default-db)); a simple event that places a token in the database (reg-event-db :store-login (fn [db [_ token]] (assoc db :token token)))

同样, 这很简单-我们定义了两个事件。首先是初始化我们的数据库。 (看看它如何忽略它的两个参数?我们总是使用default-db初始化数据库!)第二个是一旦获得令牌就存储它。
请注意, 这些事件都没有副作用-根本没有外部调用, 也没有I / O!这对于保持神圣的Redux流程的神圣性非常重要。不要让它变得不纯洁, 以免你希望Redux的愤怒降临在你身上。
最后, 我们需要登录事件。我们将其放置在其他位置下:
(reg-event-fx :login (fn [{:keys [db]} [_ credentials]] {:request-token credentials}))(reg-fx :request-token (fn [{:keys [username password]}] (let [token (complicated-io-login-operation username password)] (rf/dispatch [:store-login token]))))

reg-event-fx函数与reg-event-db基本相似, 尽管有一些细微的差异。
  • 第一个参数不再只是数据库本身。它包含许多其他内容, 可用于管理应用程序状态。
  • 第二个参数与reg-event-db中的类似。
  • 我们不仅返回一个新的数据库, 还返回一个映射, 该映射表示此事件应发生的所有效果(” fx” )。在这种情况下, 我们简单地调用:request-token效果, 其定义如下。其他有效效果之一是:dispatch, 它仅调用另一个事件。
派发我们的效果后, 将调用:request-token效果, 该效果将执行我们长期运行的I / O登录操作。一旦完成, 它将愉快地将结果分配回事件循环, 从而完成循环!
ClojureScript教程:最终结果 所以!我们已经定义了存储抽象。该组件现在是什么样的?
(ns unearthing-clojurescript.login (:require [reagent.core :as reagent :refer [atom]] [re-frame.core :as rf])); ; -- STATE --(def username (atom nil)) (def password (atom nil)); ; -- VIEW --(defn component [] [:div [:b "Username"] [:input {:type "text" :value @username :on-change #(reset! username (-> % .-target .-value))}] [:b "Password"] [:input {:type "password" :value @password :on-change #(reset! password (-> % .-target .-value))}] [:input {:type "button" :value "Login!" :on-click #(rf/dispatch [:login {:username @username :password @password]})}]])

和我们的应用程序组件:
(ns unearthing-clojurescript.app (:require [unearthing-clojurescript.login :as login])); ; -- VIEW --(defn component [] [:div [login/component]])

最后, 在某些远程组件中访问令牌很简单:
(let [token @(rf/subscribe [:token])] ; ... )

放在一起:
Redux状态管理的顶级控制(ClojureScript教程)

文章图片
没有脚, 没有必须。
使用Redux/Re-frame重新耦合组件意味着干净的状态管理 使用Redux(通过重新框架), 我们成功地将视图组件与状态处理混乱分离了。现在扩展我们的状态抽象只是小菜一碟!
ClojureScript中的Redux确实很容易-你没有理由不尝试一下。
如果你准备好破解, 建议你查看出色的重新整理文档和我们简单的示例。我期待着你阅读下面有关此ClojureScript教程的评论。祝你好运!
【Redux状态管理的顶级控制(ClojureScript教程)】相关:使用Firebase在Angular中进行状态管理

    推荐阅读