本文概述
阅读本简介时, 你可能会想到两个主要想法:
- 什么是ClojureScript?
- 这与我的兴趣无关。
可是等等!那就是你错了, 我会向你证明。如果你愿意花10分钟的时间, 我将向你展示ClojureScript如何使编写前端和React-y应用程序变得有趣, 快速, 最重要的是_functional_。
一些ClojureScript教程先决条件
- Lisp知识不是必需的。我将尽力解释此博客文章中散布的所有代码示例!
- 但是, 如果你确实想做一点预读, 我强烈建议你https://www.braveclojure.com/, 这是一门开始使用Clojure(以及扩展名ClojureScript)的商店。
- Clojure和ClojureScript具有共同的语言-我经常将它们与Clojure [Script]同时提及。
- 我确实假设你具有React和一般前端知识的知识。
如何学习ClojureScript:简化版
因此, 你没有太多时间来学习ClojureScript, 而你只想了解整个过程。首先, 什么是ClojureScript?
在ClojureScript网站上:ClojureScript是针对Clojure的JavaScript编译器。它发出的JavaScript代码与Google Closure优化编译器的高级编译模式兼容。
除其他外, ClojureScript提供了很多功能:
- 这是一种多范式编程语言, 具有精益的功能性程序设计-众所周知, 功能性程序设计可提高代码易读性, 并帮助你以更少的代码编写更多内容。
- 默认情况下, 它支持不变性-告别了整套运行时问题!
- 它是面向数据的:代码是ClojureScript中的数据。大多数Clojure [Script]应用程序可以简化为对某些底层数据结构进行操作的一组函数, 这使调试变得简单且代码清晰易懂。
- 这很简单! ClojureScript入门很容易-没有花哨的关键字, 而且几乎没有魔术。
- 它有一个很棒的标准库。这东西应有尽有。
顺便说一句, 让我们以一个示例打开这种蠕虫罐:
(defn component
[]
[:div
"Hello, world!"])
对于不熟悉Lisp方言或ClojureScript的用户请注意:此示例中最重要的部分是:div, []和()。 :div是表示<div>元素的关键字。 []是向量, 很像Java中的ArrayList, 而()是序列, 很像LinkedList。我将在本文后面的内容中更详细地介绍这一点!
这是ClojureScript中React组件的最基本形式。就是这样-只是一个关键字, 一个字符串和一堆列表。
sha!你说的是, 与JSX或TSX中的” hello world”并没有太大区别:
function component() {
return (
<div>
"Hello, world!"
</div>
);
}
但是, 即使从这个基本示例中我们也可以发现一些关键差异:
- 没有嵌入式语言。 ClojureScript示例中的所有内容都可以是字符串, 关键字或列表。
- 简洁;列表提供了我们所需的所有表现力, 而没有HTML关闭标签的冗余。
这两个小差异不仅对你编写代码的方式而且对你表达自己的方式都具有巨大的影响!
你问那怎么样?让我们跳入战斗, 看看ClojureScript还为我们提供了什么……
有关:
- Elm编程语言入门
- Elixir编程语言入门
组成模块
在整个ClojureScript教程中, 我将尽量不深入探讨Clojure [Script]的出色之处(很多事情, 但我离题)。尽管如此, 覆盖一些基本概念还是很有用的, 因此有可能掌握我们在这里可以做的事情的广度。
对于那些经验丰富的Clojuristas和Lispians, 请随时跳到下一部分!
首先需要涉及三个主要概念:
关键词
Clojure [Script]有一个称为关键字的概念。它位于常量字符串(例如, Java)和键之间。它们是自我评估的符号标识符。
例如, 关键字:cat将始终引用:cat, 而不会引用任何其他内容。就像在Java中一样, 你可能会说:
private static const String MY_KEY = "my_key";
// ...
myMap.put(MY_KEY, thing);
// ...
myMap.get(MY_KEY);
…在Clojure中, 你只需拥有:
(assoc my-map :my-key thing)
(my-map :my-key) ; equivalent to (:my-key my-map) ...nice and flexible!
还要注意:在Clojure中, 映射既是一个集合(值的键, 就像Java HashMap一样), 也是一个访问其内容的函数。整齐!
列表
Clojure [Script]是Lisp的方言, 意味着它非常重视列表。正如我之前提到的, 有两件事要注意:
- []是一个向量, 非常类似于ArrayList。
- ()是一个序列, 非常类似于LinkedList。
要在Clojure [Script]中构建事物列表, 请执行以下操作:
[1 2 3 4]
["hello" "world"]
["my" "list" "contains" 10 "things"] ; you can mix and match types
; in Clojure lists!
对于序列, 有一点不同:
'(1 2 3 4)
'("hello" "world")
下一节将说明前置的’。
函数
最后, 我们有功能。 Clojure [Script]中的函数是键入的序列, 且不带前缀’。该列表的第一个元素是函数本身, 接下来的所有元素都是参数。例如:
(+ 1 2 3 4) ; -> 10
(str "hello" " " "world") ; -> "hello world"
(println "hi!") ; prints "hi!" to the console
(run-my-function) ; runs the function named `run-my-function`
这种行为的一个必然结果是, 你可以在不实际执行功能的情况下建立功能的定义!评估程序时, 只会执行”裸”序列。
(+ 1 1) ; -> 2
'(+ 1 1); -> a list of a function and two numbers
稍后将变得重要!
可以通过几种方式定义函数:
; A normal function definition, assigning the function
; to the symbol `my-function`
(defn my-function
[arg1 arg2]
(+ arg1 arg2))
; An anonymous function that does the same thing as the above
(fn [arg1 arg2] (+ arg1 arg2))
; Another, more concise variation of the above
#(+ %1 %2)
进一步考试
因此, 既然我们已经介绍了基础知识, 那么让我们进一步深入研究一下细节, 看看这里发生了什么。
通常使用称为Reagent的库在ClojureScript中进行反应。 Reagent使用Hiccup及其语法表示HTML。从打ic仓库的Wiki:
“打ic会像这样转换Clojure数据结构:”
[:a {:href "http://github.com"} "GitHub"]
“插入这样的HTML字符串中:”
<a href="http://github.com">GitHub</a>
简而言之, 列表的第一个元素成为HTML元素类型, 其余元素成为该元素的内容。 (可选)你可以提供属性映射, 然后将其附加到该元素。
只需将元素嵌套在其父级列表中, 即可将它们彼此嵌套!通过示例最容易看出:
[:div
[:h1 "This is a header"]
[:p "And in the next element we have 1 + 1"]
[:p (+ 1 1)]]
请注意, 我们如何在结构中放置任何旧函数或通用Clojure语法, 而不必显式声明嵌入方法。毕竟, 这只是一个清单!
甚至更好的是, 这在运行时对结果有什么影响?
[:div
[:h1 "This is a header"]
[:p "And in the next element we have 1 + 1"]
[:p 2]]
当然还有关键字和内容列表!没有有趣的类型, 没有神奇的隐藏方法。这只是一个简单的旧清单。你可以随意拼接并播放此列表-所见即所得。
通过Hiccup进行布局, Reagent进行逻辑和事件处理, 我们最终得到了一个功能齐全的React环境。
一个更复杂的例子
好吧, 让我们通过一些组件将其结合在一起。 React(和Reagent)的神奇之处之一是你将视图和布局逻辑划分为模块, 然后可以在整个应用程序中重复使用它们。
假设我们创建一个显示按钮和一些简单逻辑的简单组件:
; widget.cljs
(defn component
[polite?]
[:div
[:p (str "Do not press the button" (when polite? ", please."))]
[:input {:type "button"
:value "PUSH ME"
:on-click #(js/alert "What did I tell you?")}]])
关于命名的快速说明:Clojure中的模块通常使用命名空间, 因此widget.cljs可能会导入到命名空间小部件下。这意味着顶层组件功能将作为小部件/组件访问。我希望每个模块只有一个顶级组件, 但这是一种样式首选项-你可能更喜欢将组件函数命名为polite-component或widget-component。
这个简单的组件为我们提供了一个可选的礼貌小部件。 (何时礼貌?”请”。)的计算结果为”请”。什么时候有礼貌? == true, 如果为false, 则为nil。
现在, 将其嵌入到我们的app.cljs中:
(defn app
[]
[:div
[:h1 "Welcome to my app"]
[widget/component true]])
在这里, 我们通过将小部件称为列表的第一项来将小部件嵌入到我们的应用程序组件中, 就像HTML关键字一样!然后, 我们可以通过将它们作为子元素或参数提供给同一列表的其他元素, 从而将其传递给组件。在这里, 我们只是传递true, 所以在我们的小部件中客气吗? == true, 因此我们得到了礼貌的版本。
如果我们现在评估我们的应用程序功能, 我们将获得以下信息:
[:div
[:h1 "Welcome to my app"]
[widget/component true]] ; <- widget/component would look more like a
; function reference, but I have kept it
; clean for legibility.
注意如何评估小部件/组件! (如果你感到困惑, 请参阅”功能”部分。)
如果DOM树中的组件已更新, 则仅对其进行评估(并因此将其转换为幕后的真实React对象), 这使事情变得井井有条, 并减少了你在任何时间点都要处理的复杂性。
有关这些主题的更多详细信息, 请参阅”试剂”文档。
一直列出
此外, 请注意DOM如何只是列表列表, 而组件只是返回列表列表的函数。为什么在学习ClojureScript时如此重要?
因为你可以对函数或列表执行任何操作, 所以你可以对组件执行操作。
在这里, 你可以使用ClospureScript之类的Lisp方言开始获得复合收益:你的组件和HTML元素成为一流的对象, 你可以像处理任何其他普通数据一样操作!我再说一遍:
组件和HTML元素是Clojure语言中一流的支持对象!
没错, 你听到了我的声音。就像Lisps旨在处理列表一样(提示:确实如此)。
这包括以下内容:
- 映射编号列表的元素:
(def words ["green" "eggs" "and" "ham"])
(defn li-shout
[x]
[:li (string/uppercase x))
(concat [:ol] (map li-shout words)
; becomes
[:ol
[:li "GREEN"]
[:li "EGGS"]
[:li "AND"]
[:li "HAM"]]
- 包装组件:
; in widget.cljs
(defn greeting-component
[name]
[:div
[:p (str "Hiya " name "!")]])
; ...
(def shouty-greeting-component
#(widget/greeting-component (string/uppercase %)))
(defn app
[]
[:div
[:h1 "My App"]
[shouty-greeting-component "Luke"]]) ; <- will show Hiya LUKE!
- 注入属性:
(def default-btn-attrs
{:type "button"
:value "I am a button"
:class "my-button-class"})
(defn two-button-component
[]
[:div
[:input (assoc default-btn-attrs
:on-click #(println "I do one thing"))]
[:input (assoc default-btn-attrs
:on-click #(println "I do a different thing"))]])
处理像列表和映射这样的普通旧数据类型比像类的任何东西都要简单得多, 并且从长远来看, 最终将变得更加强大!
模式出现
好吧, 让我们回顾一下。到目前为止, 我们的ClojureScript教程显示了什么?
- 一切都简化为最简单的功能-元素只是列表, 而组件只是返回元素的函数。
- 因为组件和元素是一流的对象, 所以我们可以用更少的钱写更多的东西。
这两点恰好适合Clojure和功能编程的精神-代码是要处理的数据, 而复杂性是通过连接较不复杂的部分而建立的。我们将程序(此示例中的网页)作为数据(列表, 函数, 地图)呈现出来, 并一直保持这种方式, 直到Reagent接管并将其转化为React代码的最后一刻。这使我们的代码可重复使用, 并且最重要的是, 只需很少的魔术就能非常容易地阅读和理解。
变得时尚
现在, 我们知道了如何使应用程序具有一些基本功能, 接下来让我们继续介绍如何使其看起来更美观。有两种方法可以解决此问题, 最简单的方法是使用样式表并在组件中引用其类:
.my-class {
color: red;
}
[:div {:class "my-class"}
"Hello, world!"]
这将完全符合你的期望, 并向我们展示一个美丽的红色” Hello, world!”文本。
但是, 为什么要把所有的麻烦都放在视图和逻辑代码中, 然后再将样式分离到样式表中, 这不仅麻烦你现在不仅必须在两个不同的地方查看, 而且还要处理两个不同的地方语言呢!
为什么不将CSS作为代码编写在组件中(请参见此处的主题?)。这将给我们带来很多好处:
- 定义组件的所有内容都在同一位置。
- 通过巧妙的生成, 可以确保类名的唯一性。
- CSS可以是动态的, 可以随着我们数据的变化而变化。
我个人最喜欢的CSS编码风格是Clojure样式表(cljss)。嵌入式CSS如下所示:
;; -- STYLES ------------------------------------------------------------
(defstyles component-style []
{:color "red"
:width "100%"})
;; -- VIEW --------------------------------------------------------------
(defn component
[]
[:div {:class (component-style)}
"Hello, world!"])
defstyles创建一个函数, 该函数将为我们生成一个唯一的类名(这对于导入我们组件的任何人都非常有用)。
cljss可以为你做很多其他事情(组成样式, 动画, 元素替代等), 在此不再赘述。我建议你自己检查一下!
组装ClojureScript应用程序的各个部分
最后, 需要将所有这些胶粘在一起的胶水。幸运的是, 除了项目文件和index.html外, 此处的模板最少。
你需要:
- 你的项目定义文件project.clj。它是任何Clojure项目的主要内容, 它定义了你的依赖项(甚至直接来自GitHub)以及其他构建属性(类似于build.gradle或package.json)。
- 一个index.html, 它充当Reagent应用程序的绑定点。
- 一些用于开发环境的设置代码, 最后用于启动Reagent应用程序。
你可以在GitHub上找到此ClojureScript教程的完整代码示例。
就这样(目前)。希望我至少引起了你的好奇心, 无论是检查Lisp的方言(Clojure [Script]还是其他方法), 甚至是尝试制作自己的Reagent应用程序!我向你保证, 你不会后悔的。
和我一起进入本文的后续工作, 进入状态, 在这里我谈论使用重新框架的状态管理-向ClojureScript中的Redux致以问候!