作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
卢克·汤姆林

Luke拥有计算机科学和数学硕士学位,擅长函数式编程. 在谷歌实习开启了他强大的开发生涯.

分享
< div >

欢迎回来参加第二部分 挖掘ClojureScript的! 在这篇文章中, 我将介绍认真使用ClojureScript的下一个重要步骤:状态管理——在本例中, 使用的反应.

对于前端软件,状态管理是一件大事. 开箱即用,有几种方法可以处理状态in 反应:

  • 将状态保持在最高级别, 并将其(或特定状态的处理程序)传递给子组件.
  • 将纯度抛到窗外,使用全局变量或某种lovecraft形式的依赖注入.

一般来说,这两者都不好. 将状态保存在顶层是相当简单的, 但是,将应用程序状态传递给每个需要它的组件需要大量的开销.

相比之下, 使用全局变量(或状态的其他原始版本)可能导致难以跟踪的并发性问题, 导致组件在您期望更新时没有更新, 反之亦然.

那么如何解决这个问题呢? 对于熟悉反应的人来说,您可能已经尝试过回来的,一个状态容器 JavaScript 应用程序. 你可能是自己发现的, 大胆探索可管理的国家维护体系. 或者您可能只是在阅读JavaScript和其他web工具时偶然发现了它.

不管人们如何看待回来的, 根据我的经验,他们通常会有两种想法:

  • “我觉得我必须使用它,因为每个人都说我必须使用它.”
  • “我真的不完全明白为什么这样更好.”

一般来说,回来的提供了一个抽象,使状态管理适合于 无功 反应的本质. 通过将所有的有状态性卸载到回来的这样的系统中,您可以保留 纯度 的反应. 这样你就不会那么头疼了,而且通常也更容易推理了.

对于那些刚接触Clojure的人

虽然这可能无法帮助您完全从头开始学习ClojureScript, 在这里,我将至少回顾一下Clojure[Script]中的一些基本状态概念。. 如果你已经 一个经验丰富的克洛朱利安人!

回想一下Clojure的一个基础,它也适用于ClojureScript:默认情况下, 数据是不可变的. This is great for developing 和 有 guarantees that what you create at timestep N is still the same at timestep > N. ClojureScript还为我们提供了一种方便的方式来拥有可变状态,如果我们需要的话 原子 概念.

An 原子 在ClojureScript中非常类似于 AtomicReference 在Java中:它提供了一个新对象,通过并发性保证锁定其内容. 和Java一样, 从那时起,你可以把任何你喜欢的东西放在这个物体上, 这个原子将是你想要的任何东西的原子引用.

一旦你有了 原子,可以在其中自动设置一个新值 重置! 函数(请注意 ! 在函数中(在Clojure语言中,这通常用于表示操作是有状态的或不纯的).

还要注意,与java不同,clojure并不关心您在 原子. 它可以是字符串、列表或对象. 动态打字,宝贝!

(def my-mutable-map (原子 {})) ; recall that {} means an empty map in Clojure

(println @my-mutable-map) ; You 'dereference' an 原子 using @
                          ; -> this prints {}

(重置! my-mutable-map {:hello “有"}) ; 原子ically set the 原子
(重置! 我的可变映射"你好!")  ; don't forget Clojure is dynamic :)

试剂用它自己扩展了原子的概念 原子. (如果您不熟悉Reagent,请查看 之前的帖子.)这与ClojureScript的行为相同 原子, 除了它还会触发Reagent中的渲染Events,就像反应的内置状态存储一样.

一个例子:

(ns的例子
  (:要求[试剂.core :refer [原子]])) ; in this module, 原子 now refers
                                           ; to reagent's 原子.

原子(原子)世界!"))

(defn组件
  []
  [: div
    [:span "Hello, " @my-原子]
    [:input {:type "button"
             :value“按我”!"
             :点击#(重置! My-原子”!")}]])

这将显示一个单

包含一个 说:“你好,世界!和一个纽扣,正如你所料. 按下那个按钮就会自动变异 my-原子 包含 “有!". 这将触发重新绘制组件,导致span显示“Hello, there”!”而不是.

概述由回来的和Reagent处理的状态管理.

这对当地人来说似乎很简单, 级变异, 但是,如果我们有一个更复杂的应用程序,它有多个抽象层次? 或者如果我们需要在多个子组件和它们的子组件之间共享公共状态?

一个更复杂的例子

让我们通过一个例子来探讨这个问题. 这里我们将实现一个粗略的登录页面:

(ns unearthing-clojurescript.登录
  (:要求[试剂.核心:作为试剂:参考[原子]])

;; -- STATE --

(def username (原子 nil))
(def password (原子 nil))

;; -- VIEW --

(defn组件
  (在登录)
  [: div
   [b“用户名”):
   [:input {:type "text"
            @用户名:价值
            :变化#(重置! username (-> % .—target .值)})
   [b“密码”):
   [:input {:type "password"
            :价值@password
            :变化#(重置! password (-> % .—target .值)})
   [:input {:type "button"
            :价值”登录!"
            :on-click #(on-登录 @username @password)}]]

然后,我们将在我们的主组件中托管这个登录组件 应用程序.cljs,像这样:

(ns unearthing-clojurescript.应用程序
  (:要求[unearthing-clojurescript.登录:作为登录]))

;; -- STATE

(def token (原子 nil))

;; -- LOGIC --

(defn do-登录-io
  (用户名密码)
  (let [t (complicated-io-登录-operation username - password)]
    (重置! 令牌t)))
    
;; -- VIEW --

(defn组件
  []
  [: div
    [登录/组件do-登录-io]])

预期的工作流程如下:

  1. 我们等待用户输入他们的用户名和密码,然后点击提交.
  2. 这将触发我们 do-登录-io 函数在父组件中.
  3. do-登录-io 函数执行一些I/O操作(例如在服务器上登录并检索令牌).

如果此操作阻塞, 那我们就麻烦大了, 因为我们的应用程序被冻结了——如果不是的话, 然后我们要考虑异步!

另外, 现在,我们需要将这个令牌提供给希望对服务器执行查询的所有子组件. 代码重构变得更加困难了!

最后,我们的组件现在不再是纯粹的 无功它现在是管理应用程序其余部分状态的同谋, 触发I/O,通常有点讨厌.

ClojureScript教程:输入回来的

回来的是一根魔杖,可以让你所有的国家梦想成真. 如果实现得当,它提供了一种安全、快速且易于使用的状态共享抽象.

回来的的内部工作原理(及其背后的理论)在某种程度上超出了本文的范围. 而不是, 我将深入研究一个使用ClojureScript的工作示例, 这应该能在某种程度上证明它的能力!

在我们的语境中, 回来的 is implemented by one of the many ClojureScript libraries available; this one called 圆谎. 它为回来的提供了一个clojure化的包装器,(在我看来)使用起来绝对令人愉悦.

最基本的

回来的提升应用程序状态,使组件保持轻量级. 回来的ified组件只需要考虑:

  • 它是什么样子的
  • 它消耗什么数据
  • 它触发了什么Events

其余的都在幕后处理.

为了强调这一点,让我们重新定义上面的登录页面.

数据库

首先要做的是:我们需要决定我们的应用程序模型将是什么样子. 我们通过定义 形状 我们的数据,这些数据将在整个应用程序中被访问.

一个好的经验法则是,如果数据需要跨多个回来的组件使用, 或者需要长期存在(就像我们的令牌一样), 然后应该存储在数据库中. 相比之下, 如果数据是组件的本地数据(比如我们的用户名和密码字段),那么它应该作为本地组件状态存在,而不是存储在数据库中.

让我们创建我们的数据库样板并指定我们的令牌:

(ns unearthing-clojurescript.状态.db
  (:要求[cljs.规范.[a]
            [圆谎.核心:as 圆谎]))

(s/def::token字符串?)
(s/def::db (s/keys: opt-un [::token]))

(def default-db
  {:令牌零})

这里有几个有趣的地方值得注意:

  • 我们使用 Clojure的 规范 图书馆 to 描述 我们的数据应该是什么样子. 这在像Clojure[Script]这样的动态语言中尤其适用。.
  • 对于这个例子, 我们只跟踪一个全局令牌,它将代表我们的用户一旦登录. 这个令牌是一个简单的字符串.
  • 但是,在用户登录之前,我们没有令牌. 这是用 : opt-un 关键字,代表“可选的,非限定的”.(在Clojure中,常规关键字是这样的 :猫,而限定关键字可能是这样的 猫:动物/. 限定通常在模块级别进行,这可以防止不同模块中的关键字相互攻击.)
  • 最后,我们指定数据库的默认状态,这就是数据库初始化的方式.

在任何时候,我们都应该确信数据库中的数据与这里的规范匹配.

Subscriptions

现在我们已经描述了我们的数据模型,我们需要反映我们的 视图 显示数据. 我们已经描述了回来的组件中的视图,现在我们只需要将视图连接到数据库.

带回来的, 我们不直接访问数据库——这可能导致生命周期和并发性问题. 相反,我们将关系注册到数据库的一个方面 Subscriptions.

Subscriptions告诉reframe(和Reagent)我们依赖于数据库的一部分, 如果这部分被改变了, 那么我们的回来的组件应该被重新渲染.

Subscriptions的定义非常简单:

(ns unearthing-clojurescript.状态.潜艇
  (:要求[圆谎.核心:参考[reg-sub]]))

(reg-sub
  :token                         ; <- the name of the 潜艇cription
  (fn [{:keys [token] :as db} _] ; first argument is the database, second argument is any
    token))                      ; args passed to the 潜艇cribe function (not used here)

在这里,我们注册一个Subscriptions——对令牌本身. Subscriptions只是Subscriptions的名称, 以及从数据库中提取该项的函数. 我们可以对这个值做任何我们想做的, 和 mutate the 视图 as 多 as we like here; however, 在这种情况下, 我们只是从数据库中提取令牌并返回它.

有很多, 您可以使用Subscriptions做更多的事情—例如在数据库的子部分上定义视图,以便在重新呈现时更严格地限定范围—但是我们现在将保持简单!

Events

我们有数据库,我们有数据库的视图. 现在我们需要触发一些Events! 在这个例子中,我们有两种Events:

  • 纯粹的Events(有 no 将新令牌写入数据库的副作用.
  • I/OEvents( 通过一些客户端交互出去请求我们的令牌的副作用.

我们从简单的开始. reframe甚至为这类Events提供了一个函数:

(ns unearthing-clojurescript.状态.Events
  (:要求[圆谎.Core:参考[reg-event-db reg-event-fx reg-fx]:as rf]
            [unearthing-clojurescript.状态.Db:参考[default-db]])

; our start up event that initialises the database.
; we'll trigger this in our core.cljs
(reg-event-db
  : initialise-db
  [_] [_]
    default-db))

; a simple event that places a token in the database
(reg-event-db
  :存储登录
  (fn [db [_ token]]]
    (assoc db:token token))

同样,这里非常简单——我们定义了两个Events. 第一个用于初始化数据库. (看看它是如何忽略它的两个论点的? 初始化数据库时总是使用 default-db!)第二个是用于存储我们的令牌,一旦我们得到它.

注意,这两个Events都没有副作用——没有外部调用,根本没有I/O! 这对于保持神圣的回来的进程的神圣性是非常重要的. 不要让它变得不纯洁以免你希望雷杜克斯的愤怒降临到你身上.

最后,我们需要登录Events. 我们把它放在其他的下面:

(reg-event-fx
  :登录
  (fn [{:keys [db]} [_ credentials]]
    {:请求令牌凭证}))

(reg-fx
  :请求令牌
  (fn [{:keys[用户名密码]}]
    (let [token (complicated-io-登录-operation username - password)]
      (rf/dispatch [:存储登录 token]))))

reg-event-fx 功能很大程度上类似于 reg-event-db,尽管有一些细微的差别.

  • 第一个参数不再仅仅是数据库本身. 它包含了许多其他可以用来管理应用程序状态的东西.
  • 第二个参数很像 reg-event-db.
  • 而不仅仅是归还新的 db, 相反,我们返回一个表示该Events应该发生的所有效果(“fx”)的映射. 在本例中,我们简单地调用 :请求令牌 效果,定义如下. 另一个有效的效果是 :调度,它只是调用另一个Events.

一旦我们的效果消散,我们的 :请求令牌 effect,它执行长时间运行的i /O登录操作. 一旦完成, 它愉快地将结果分派回Events循环, 这样循环就完成了!

ClojureScript教程:最终结果

So! 我们已经定义了存储抽象. 组件现在是什么样子?

(ns unearthing-clojurescript.登录
  (:要求[试剂.核心:作为试剂:参考[原子]]
            [圆谎.核心:作为rf])

;; -- STATE --

(def username (原子 nil))
(def password (原子 nil))

;; -- VIEW --

(defn组件
  []
  [: div
   [b“用户名”):
   [:input {:type "text"
            @用户名:价值
            :变化#(重置! username (-> % .—target .值)})
   [b“密码”):
   [:input {:type "password"
            :价值@password
            :变化#(重置! password (-> % .—target .值)})
   [:input {:type "button"
            :价值”登录!"
            :on-click #(rf/dispatch [:登录 {:username @username] 
                                             :密码@password]}}]])

我们的应用程序组件:

(ns unearthing-clojurescript.应用程序
  (:要求[unearthing-clojurescript.登录:作为登录]))
   
;; -- VIEW --

(defn组件
  []
  [: div
    [登录/组件]])

最后,在某个远程组件中访问我们的令牌就像这样简单:

(let [token @(rf/潜艇cribe [:token])]
  ; ...
  )

把它们放在一起:

在登录示例中,本地状态和全局(回来的)状态如何工作.

没有大惊小怪,没有混乱.

用回来的/ reframe解耦组件意味着干净的状态管理

使用回来的(通过重帧), 我们成功地将视图组件从混乱的状态处理中解耦了. 扩展我们的状态抽象现在是小菜一碟!

在ClojureScript中的回来的 is 这么简单——你没有理由不去尝试一下.

如果你已经准备好了,我建议你去看看 神奇的重新框架文件我们的简单工作示例. 我期待着阅读您对下面的ClojureScript教程的评论. 祝你好运!

了解基本知识

  • 什么是回来的状态?

    回来的状态指的是回来的用来管理应用程序状态的单个存储. 这个存储完全由回来的控制,不能从应用程序本身直接访问.

  • 回来的是Events源吗?

    不,回来的是一种独立于Events源模式的技术. 回来的的灵感来自另一种叫做Flux的技术.

  • 什么是回来的容器?

    回来的容器(或者简称为“容器”)是Subscriptions回来的状态的反应组件, 当该部分状态发生变化时接收更新.

  • 回来的是一个框架吗?

    是的,回来的在web应用程序中提供了一个围绕状态管理的框架.

  • 什么是ClojureScript?

    ClojureScript是一个针对JavaScript的Clojure编译器. 它通常用于使用Clojure语言构建web应用程序和库.

聘请Toptal这方面的专家.
现在雇佣

作者简介

Luke拥有计算机科学和数学硕士学位,擅长函数式编程. 在谷歌实习开启了他强大的开发生涯.

世界级的文章,每周发一次.

Subscriptions意味着同意我们的 隐私政策

< div >

世界级的文章,每周发一次.

Subscriptions意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® 社区.