个性化阅读
专注于IT技术分析

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

点击下载

本文概述

欢迎回来阅读《出土的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和Reagent处理的状态管理概述。

对于局部的, 组件级的突变来说, 这似乎很简单, 但是如果我们有一个具有多个抽象层的更复杂的应用程序, 该怎么办?还是如果我们需要在多个子组件及其子组件之间共享公共状态?

一个更复杂的例子

让我们用一个例子来探讨一下。在这里, 我们将实现一个粗糙的登录页面:

(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)状态如何工作。

没有脚, 没有必须。

使用Redux/Re-frame重新耦合组件意味着干净的状态管理

使用Redux(通过重新框架), 我们成功地将视图组件与状态处理混乱分离了。现在扩展我们的状态抽象只是小菜一碟!

ClojureScript中的Redux确实很容易-你没有理由不尝试一下。

如果你准备好破解, 建议你查看出色的重新整理文档和我们简单的示例。我期待着你阅读下面有关此ClojureScript教程的评论。祝你好运!

相关:使用Firebase在Angular中进行状态管理

赞(0)
未经允许不得转载:srcmini » Redux状态管理的顶级控制:ClojureScript教程

评论 抢沙发

评论前必须登录!