本文概述
- 对于Clojure的新手
- ClojureScript教程:输入Redux
- ClojureScript教程:最终结果
- 使用Redux/Re-frame解耦组件意味着干净的状态管理
使用前端软件, 状态管理非常重要。开箱即用, 有两种方法可以在React中处理状态:
- 将状态保持在顶层, 并将其(或特定状态的处理程序)传递给子组件。
- 将纯净的东西扔出窗口, 并具有全局变量或某种Lovecraftian形式的依赖项注入。
相比之下, 拥有全局变量(或状态的其他原始版本)会导致难以跟踪的并发问题, 从而导致组件在你期望的时候不会更新, 反之亦然。
那么如何解决呢?对于那些熟悉React的人来说, 你可能已经尝试过Redux, 这是JavaScript应用程序的状态容器。你可能出于自己的意愿而发现了这个问题, 大胆地寻找可维护状态的可管理系统。或者, 你可能在阅读JavaScript和其他Web工具时偶然发现了它。
无论人们最终如何看待Redux, 根据我的经验, 他们通常都会想到两个想法:
- “ 我觉得我必须使用它, 因为每个人都说我必须使用它。”
- “ 我真的不完全理解为什么这样做会更好。”
对于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!” 。代替。
文章图片
对于局部的, 组件级的突变来说, 这似乎很简单, 但是如果我们有一个具有多个抽象层的更复杂的应用程序, 该怎么办?还是如果我们需要在多个子组件及其子组件之间共享公共状态?
一个更复杂的例子
让我们用一个例子来探讨一下。在这里, 我们将实现一个粗糙的登录页面:
(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]])
因此, 预期的工作流程为:
- 我们等待用户输入用户名和密码, 然后点击提交。
- 这将触发父组件中的do-login-io函数。
- 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, 它仅调用另一个事件。
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/Re-frame重新耦合组件意味着干净的状态管理 使用Redux(通过重新框架), 我们成功地将视图组件与状态处理混乱分离了。现在扩展我们的状态抽象只是小菜一碟!
ClojureScript中的Redux确实很容易-你没有理由不尝试一下。
如果你准备好破解, 建议你查看出色的重新整理文档和我们简单的示例。我期待着你阅读下面有关此ClojureScript教程的评论。祝你好运!
【Redux状态管理的顶级控制(ClojureScript教程)】相关:使用Firebase在Angular中进行状态管理
推荐阅读
- 挖掘用于前端开发的ClojureScript
- F#教程(如何构建完整的F#应用程序)
- 作为JS开发人员,这就是让我彻夜难眠的原因
- ActiveResource.js(快速为你的JSON API构建强大的JavaScript SDK)
- React教程(如何入门及其比较(1))
- 你的WordPress API开发人员未使用的五种经过久经考验的技术
- 老师节日快乐!教师节祝福微信gif表情包_微信
- 适合在教师节这一天公布的微信朋友圈图文说说_微信
- 简聊怎样用?简聊注册与登录运用图文详细教程_其它聊天