作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Luke拥有计算机科学和数学硕士学位,擅长函数式编程. 在谷歌实习开启了他强大的开发生涯.
欢迎回来参加第二部分 挖掘ClojureScript的! 在这篇文章中, 我将介绍认真使用ClojureScript的下一个重要步骤:状态管理——在本例中, 使用的反应.
对于前端软件,状态管理是一件大事. 开箱即用,有几种方法可以处理状态in 反应:
一般来说,这两者都不好. 将状态保存在顶层是相当简单的, 但是,将应用程序状态传递给每个需要它的组件需要大量的开销.
相比之下, 使用全局变量(或状态的其他原始版本)可能导致难以跟踪的并发性问题, 导致组件在您期望更新时没有更新, 反之亦然.
那么如何解决这个问题呢? 对于熟悉反应的人来说,您可能已经尝试过回来的,一个状态容器 JavaScript 应用程序. 你可能是自己发现的, 大胆探索可管理的国家维护体系. 或者您可能只是在阅读JavaScript和其他web工具时偶然发现了它.
不管人们如何看待回来的, 根据我的经验,他们通常会有两种想法:
一般来说,回来的提供了一个抽象,使状态管理适合于 无功 反应的本质. 通过将所有的有状态性卸载到回来的这样的系统中,您可以保留 纯度 的反应. 这样你就不会那么头疼了,而且通常也更容易推理了.
虽然这可能无法帮助您完全从头开始学习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-原子”!")}]])
这将显示一个单 这对当地人来说似乎很简单, 级变异, 但是,如果我们有一个更复杂的应用程序,它有多个抽象层次? 或者如果我们需要在多个子组件和它们的子组件之间共享公共状态? 让我们通过一个例子来探讨这个问题. 这里我们将实现一个粗略的登录页面: 然后,我们将在我们的主组件中托管这个登录组件 预期的工作流程如下: 如果此操作阻塞, 那我们就麻烦大了, 因为我们的应用程序被冻结了——如果不是的话, 然后我们要考虑异步! 另外, 现在,我们需要将这个令牌提供给希望对服务器执行查询的所有子组件. 代码重构变得更加困难了! 最后,我们的组件现在不再是纯粹的 无功它现在是管理应用程序其余部分状态的同谋, 触发I/O,通常有点讨厌. 回来的是一根魔杖,可以让你所有的国家梦想成真. 如果实现得当,它提供了一种安全、快速且易于使用的状态共享抽象. 回来的的内部工作原理(及其背后的理论)在某种程度上超出了本文的范围. 而不是, 我将深入研究一个使用ClojureScript的工作示例, 这应该能在某种程度上证明它的能力! 在我们的语境中, 回来的 is implemented by one of the many ClojureScript libraries available; this one called 圆谎. 它为回来的提供了一个clojure化的包装器,(在我看来)使用起来绝对令人愉悦. 回来的提升应用程序状态,使组件保持轻量级. 回来的ified组件只需要考虑: 其余的都在幕后处理. 为了强调这一点,让我们重新定义上面的登录页面. 首先要做的是:我们需要决定我们的应用程序模型将是什么样子. 我们通过定义 形状 我们的数据,这些数据将在整个应用程序中被访问. 一个好的经验法则是,如果数据需要跨多个回来的组件使用, 或者需要长期存在(就像我们的令牌一样), 然后应该存储在数据库中. 相比之下, 如果数据是组件的本地数据(比如我们的用户名和密码字段),那么它应该作为本地组件状态存在,而不是存储在数据库中. 让我们创建我们的数据库样板并指定我们的令牌: 这里有几个有趣的地方值得注意: 在任何时候,我们都应该确信数据库中的数据与这里的规范匹配. 现在我们已经描述了我们的数据模型,我们需要反映我们的 视图 显示数据. 我们已经描述了回来的组件中的视图,现在我们只需要将视图连接到数据库. 带回来的, 我们不直接访问数据库——这可能导致生命周期和并发性问题. 相反,我们将关系注册到数据库的一个方面 Subscriptions. Subscriptions告诉reframe(和Reagent)我们依赖于数据库的一部分, 如果这部分被改变了, 那么我们的回来的组件应该被重新渲染. Subscriptions的定义非常简单: 在这里,我们注册一个Subscriptions——对令牌本身. Subscriptions只是Subscriptions的名称, 以及从数据库中提取该项的函数. 我们可以对这个值做任何我们想做的, 和 mutate the 视图 as 多 as we like here; however, 在这种情况下, 我们只是从数据库中提取令牌并返回它. 有很多, 多 您可以使用Subscriptions做更多的事情—例如在数据库的子部分上定义视图,以便在重新呈现时更严格地限定范围—但是我们现在将保持简单! 我们有数据库,我们有数据库的视图. 现在我们需要触发一些Events! 在这个例子中,我们有两种Events: 我们从简单的开始. reframe甚至为这类Events提供了一个函数: 同样,这里非常简单——我们定义了两个Events. 第一个用于初始化数据库. (看看它是如何忽略它的两个论点的? 初始化数据库时总是使用 注意,这两个Events都没有副作用——没有外部调用,根本没有I/O! 这对于保持神圣的回来的进程的神圣性是非常重要的. 不要让它变得不纯洁以免你希望雷杜克斯的愤怒降临到你身上. 最后,我们需要登录Events. 我们把它放在其他的下面: 的 一旦我们的效果消散,我们的 So! 我们已经定义了存储抽象. 组件现在是什么样子? 我们的应用程序组件: 最后,在某个远程组件中访问我们的令牌就像这样简单: 把它们放在一起: 没有大惊小怪,没有混乱. 使用回来的(通过重帧), 我们成功地将视图组件从混乱的状态处理中解耦了. 扩展我们的状态抽象现在是小菜一碟! 在ClojureScript中的回来的 is 这么简单——你没有理由不去尝试一下. 如果你已经准备好了,我建议你去看看 神奇的重新框架文件 和 我们的简单工作示例. 我期待着阅读您对下面的ClojureScript教程的评论. 祝你好运! 说:“你好,世界!和一个纽扣,正如你所料. 按下那个按钮就会自动变异
my-原子
包含 “有!"
. 这将触发重新绘制组件,导致span显示“Hello, there”!”而不是.
一个更复杂的例子
(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]])
do-登录-io
函数在父组件中.do-登录-io
函数执行一些I/O操作(例如在服务器上登录并检索令牌).ClojureScript教程:输入回来的
最基本的
数据库
(ns unearthing-clojurescript.状态.db
(:要求[cljs.规范.[a]
[圆谎.核心:as 圆谎]))
(s/def::token字符串?)
(s/def::db (s/keys: opt-un [::token]))
(def default-db
{:令牌零})
规范
图书馆 to 描述 我们的数据应该是什么样子. 这在像Clojure[Script]这样的动态语言中尤其适用。.: opt-un
关键字,代表“可选的,非限定的”.(在Clojure中,常规关键字是这样的 :猫
,而限定关键字可能是这样的 猫:动物/
. 限定通常在模块级别进行,这可以防止不同模块中的关键字相互攻击.)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)
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))
default-db
!)第二个是用于存储我们的令牌,一旦我们得到它.(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教程:最终结果
(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解耦组件意味着干净的状态管理
回来的状态指的是回来的用来管理应用程序状态的单个存储. 这个存储完全由回来的控制,不能从应用程序本身直接访问.
不,回来的是一种独立于Events源模式的技术. 回来的的灵感来自另一种叫做Flux的技术.
回来的容器(或者简称为“容器”)是Subscriptions回来的状态的反应组件, 当该部分状态发生变化时接收更新.
是的,回来的在web应用程序中提供了一个围绕状态管理的框架.
ClojureScript是一个针对JavaScript的Clojure编译器. 它通常用于使用Clojure语言构建web应用程序和库.
Luke拥有计算机科学和数学硕士学位,擅长函数式编程. 在谷歌实习开启了他强大的开发生涯.
世界级的文章,每周发一次.
世界级的文章,每周发一次.