作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Luke拥有计算机科学和数学硕士学位,擅长函数式编程. 在谷歌实习开启了他强大的开发生涯.
欢迎回来参加第二部分 Unearthing ClojureScript! In this post, 我将介绍认真使用ClojureScript的下一个重要步骤:状态管理——在本例中, using React.
对于前端软件,状态管理是一件大事. 开箱即用,有几种方法可以处理状态in React:
一般来说,这两者都不好. 将状态保存在顶层是相当简单的, 但是,将应用程序状态传递给每个需要它的组件需要大量的开销.
By comparison, 使用全局变量(或状态的其他原始版本)可能导致难以跟踪的并发性问题, 导致组件在您期望更新时没有更新, or vice versa.
那么如何解决这个问题呢? 对于熟悉React的人来说,您可能已经尝试过Redux,一个状态容器 JavaScript apps. 你可能是自己发现的, 大胆探索可管理的国家维护体系. 或者您可能只是在阅读JavaScript和其他web工具时偶然发现了它.
不管人们如何看待Redux, 根据我的经验,他们通常会有两种想法:
一般来说,Redux提供了一个抽象,使状态管理适合于 reactive nature of React. 通过将所有的有状态性卸载到Redux这样的系统中,您可以保留 purity of React. 这样你就不会那么头疼了,而且通常也更容易推理了.
虽然这可能无法帮助您完全从头开始学习ClojureScript, 在这里,我将至少回顾一下Clojure[Script]中的一些基本状态概念。. 如果你已经 a seasoned Clojurian!
回想一下Clojure的一个基础,它也适用于ClojureScript:默认情况下, data is immutable. This is great for developing and having guarantees that what you create at timestep N is still the same at timestep > N. ClojureScript还为我们提供了一种方便的方式来拥有可变状态,如果我们需要的话 atom
concept.
An atom
在ClojureScript中非常类似于 AtomicReference
在Java中:它提供了一个新对象,通过并发性保证锁定其内容. Just like in Java, 从那时起,你可以把任何你喜欢的东西放在这个物体上, 这个原子将是你想要的任何东西的原子引用.
Once you have your atom
,可以在其中自动设置一个新值 reset!
function (note the !
在函数中(在Clojure语言中,这通常用于表示操作是有状态的或不纯的).
还要注意,与java不同,clojure并不关心您在 atom
. 它可以是字符串、列表或对象. Dynamic typing, baby!
(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! 我的可变映射"你好!") ; don't forget Clojure is dynamic :)
试剂用它自己扩展了原子的概念 atom
. (如果您不熟悉Reagent,请查看 the post before this.)这与ClojureScript的行为相同 atom
, 除了它还会触发Reagent中的渲染事件,就像React的内置状态存储一样.
An example:
(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!")}]])
This will show a single 这对当地人来说似乎很简单, component-level mutation, 但是,如果我们有一个更复杂的应用程序,它有多个抽象层次? 或者如果我们需要在多个子组件和它们的子组件之间共享公共状态? 让我们通过一个例子来探讨这个问题. 这里我们将实现一个粗略的登录页面: 然后,我们将在我们的主组件中托管这个登录组件 预期的工作流程如下: 如果此操作阻塞, 那我们就麻烦大了, 因为我们的应用程序被冻结了——如果不是的话, 然后我们要考虑异步! Additionally, 现在,我们需要将这个令牌提供给希望对服务器执行查询的所有子组件. 代码重构变得更加困难了! 最后,我们的组件现在不再是纯粹的 reactive它现在是管理应用程序其余部分状态的同谋, 触发I/O,通常有点讨厌. Redux是一根魔杖,可以让你所有的国家梦想成真. 如果实现得当,它提供了一种安全、快速且易于使用的状态共享抽象. Redux的内部工作原理(及其背后的理论)在某种程度上超出了本文的范围. Instead, 我将深入研究一个使用ClojureScript的工作示例, 这应该能在某种程度上证明它的能力! In our context, Redux is implemented by one of the many ClojureScript libraries available; this one called re-frame. 它为Redux提供了一个clojure化的包装器,(在我看来)使用起来绝对令人愉悦. Redux提升应用程序状态,使组件保持轻量级. Reduxified组件只需要考虑: 其余的都在幕后处理. 为了强调这一点,让我们重新定义上面的登录页面. 首先要做的是:我们需要决定我们的应用程序模型将是什么样子. 我们通过定义 shape 我们的数据,这些数据将在整个应用程序中被访问. 一个好的经验法则是,如果数据需要跨多个Redux组件使用, 或者需要长期存在(就像我们的令牌一样), 然后应该存储在数据库中. By contrast, 如果数据是组件的本地数据(比如我们的用户名和密码字段),那么它应该作为本地组件状态存在,而不是存储在数据库中. 让我们创建我们的数据库样板并指定我们的令牌: 这里有几个有趣的地方值得注意: 在任何时候,我们都应该确信数据库中的数据与这里的规范匹配. 现在我们已经描述了我们的数据模型,我们需要反映我们的 view shows that data. 我们已经描述了Redux组件中的视图,现在我们只需要将视图连接到数据库. With Redux, 我们不直接访问数据库——这可能导致生命周期和并发性问题. 相反,我们将关系注册到数据库的一个方面 subscriptions. 订阅告诉reframe(和Reagent)我们依赖于数据库的一部分, 如果这部分被改变了, 那么我们的Redux组件应该被重新渲染. 订阅的定义非常简单: 在这里,我们注册一个订阅——对令牌本身. 订阅只是订阅的名称, 以及从数据库中提取该项的函数. 我们可以对这个值做任何我们想做的, and mutate the view as much as we like here; however, in this case, 我们只是从数据库中提取令牌并返回它. There is much, much 您可以使用订阅做更多的事情—例如在数据库的子部分上定义视图,以便在重新呈现时更严格地限定范围—但是我们现在将保持简单! 我们有数据库,我们有数据库的视图. 现在我们需要触发一些事件! 在这个例子中,我们有两种事件: 我们从简单的开始. reframe甚至为这类事件提供了一个函数: 同样,这里非常简单——我们定义了两个事件. 第一个用于初始化数据库. (看看它是如何忽略它的两个论点的? 初始化数据库时总是使用 注意,这两个事件都没有副作用——没有外部调用,根本没有I/O! 这对于保持神圣的Redux进程的神圣性是非常重要的. 不要让它变得不纯洁以免你希望雷杜克斯的愤怒降临到你身上. 最后,我们需要登录事件. 我们把它放在其他的下面: The 一旦我们的效果消散,我们的 So! 我们已经定义了存储抽象. 组件现在是什么样子? And our app component: 最后,在某个远程组件中访问我们的令牌就像这样简单: Putting it all together: No fuss, no muss. 使用Redux(通过重帧), 我们成功地将视图组件从混乱的状态处理中解耦了. 扩展我们的状态抽象现在是小菜一碟! 在ClojureScript中的Redux is 这么简单——你没有理由不去尝试一下. 如果你已经准备好了,我建议你去看看 神奇的重新框架文件 and our simple worked example. 我期待着阅读您对下面的ClojureScript教程的评论. Best of luck! saying “Hello, world!和一个纽扣,正如你所料. 按下那个按钮就会自动变异
my-atom
to contain "there!"
. 这将触发重新绘制组件,导致span显示“Hello, there”!” instead.
A More Complicated Example
(ns unearthing-clojurescript.login
(:require [reagent.核心:作为试剂:参考[原子]])
;; -- 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
, like so:(ns unearthing-clojurescript.app
(:要求[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
[登录/组件do-login-io]])
do-login-io
函数在父组件中.do-login-io
函数执行一些I/O操作(例如在服务器上登录并检索令牌).ClojureScript教程:输入Redux
The Basics
The Database
(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})
spec
library to describe 我们的数据应该是什么样子. 这在像Clojure[Script]这样的动态语言中尤其适用。.:opt-un
关键字,代表“可选的,非限定的”.(在Clojure中,常规关键字是这样的 :cat
,而限定关键字可能是这样的 :animal/cat
. 限定通常在模块级别进行,这可以防止不同模块中的关键字相互攻击.)Subscriptions
(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)
Events
(ns unearthing-clojurescript.state.events
(:require [re-frame.Core:参考[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
!)第二个是用于存储我们的令牌,一旦我们得到它.(reg-event-fx
:login
(fn [{:keys [db]} [_ credentials]]
{:请求令牌凭证}))
(reg-fx
:request-token
(fn [{:keys[用户名密码]}]
(let [token (complicated-io-login-operation username - password)]
(rf/dispatch [:store-login token]))))
reg-event-fx
功能很大程度上类似于 reg-event-db
,尽管有一些细微的差别.
reg-event-db
.db
, 相反,我们返回一个表示该事件应该发生的所有效果(“fx”)的映射. 在本例中,我们简单地调用 :request-token
效果,定义如下. 另一个有效的效果是 :dispatch
,它只是调用另一个事件.:request-token
effect,它执行长时间运行的i /O登录操作. Once this is finished, 它愉快地将结果分派回事件循环, thus completing the cycle!ClojureScript教程:最终结果
(ns unearthing-clojurescript.login
(:require [reagent.核心:作为试剂:参考[原子]]
[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]}}]])
(ns unearthing-clojurescript.app
(:要求[unearthing-clojurescript.login :as login]))
;; -- VIEW --
(defn component
[]
[:div
[login/component]])
(let [token @(rf/subscribe [:token])]
; ...
)
用Redux/ reframe解耦组件意味着干净的状态管理
Redux状态指的是Redux用来管理应用程序状态的单个存储. 这个存储完全由Redux控制,不能从应用程序本身直接访问.
不,Redux是一种独立于事件源模式的技术. Redux的灵感来自另一种叫做Flux的技术.
Redux容器(或者简称为“容器”)是订阅Redux状态的React组件, 当该部分状态发生变化时接收更新.
是的,Redux在web应用程序中提供了一个围绕状态管理的框架.
ClojureScript是一个针对JavaScript的Clojure编译器. 它通常用于使用Clojure语言构建web应用程序和库.
世界级的文章,每周发一次.
世界级的文章,每周发一次.
Join the Toptal® community.