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

Luke Tomlin

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

Share

当你读到这篇介绍的时候,你的脑海里可能有两个主要的想法:

  1. 什么是ClojureScript?
  2. 这与我的兴趣无关.

But wait! 这就是你错的地方——我会证明给你看. 如果你愿意花10分钟的时间, 我将向您展示ClojureScript如何使编写前端和React-y应用程序变得有趣, fast, 最重要的是,functional.

一些ClojureScript教程先决条件

  • 不需要Lisp知识. 我将尽力解释这篇博文中分散的代码示例!
  • 然而,如果你 do 如果你想做一点预读,我强烈推荐 http://www.braveclojure.com/,入门Clojure(以及扩展后的ClojureScript)的一站式商店.
  • Clojure 和ClojureScript共享一种公共语言——我经常将它们同时称为 Clojure(脚本).
  • I do 假设您具有React和通用知识 front-end know-how.

如何学习ClojureScript:简短版

所以您没有太多时间来学习ClojureScript, 你只是想看看整个事情的发展. 首先,什么是ClojureScript?

来自ClojureScript网站: ClojureScript是一个针对JavaScript的Clojure编译器. 它发出的JavaScript代码与Google Closure优化编译器的高级编译模式兼容.

除此之外,ClojureScript还提供了很多功能:

  • 它是一种多范式编程语言,具有函数式编程的特点 提高代码的可读性 同时也帮助你 用更少的代码写更多的东西.
  • It supports 不变性 默认情况下,告别一整套运行时问题!
  • 这是面向数据: 代码就是数据 在ClojureScript的. 大多数Clojure(脚本)应用程序可以简化为一组操作一些底层数据结构的函数, 这使得调试变得简单,代码变得超级清晰.
  • It’s simple! 开始使用ClojureScript很容易——没有花哨的关键字,也没有什么魔法.
  • 它有一个很棒的标准库. 这个东西什么都有.

有了这些,让我们用一个例子来打开这个棘手的问题:

(defn组件
  []
  [:div
    “你好,世界!"])

对于那些不熟悉Lisp方言或ClojureScript的人,请注意:这个示例中最重要的部分是 :div, the [], and the (). :div 关键字是否代表

element. [] 向量和an很像吗 ArrayList 在Java,以及 () 序列是不是很像 LinkedList. 我将在这篇文章的后面更详细地讨论这个问题!

这是ClojureScript中React组件的最基本形式. 就是这样,一个关键字,一个字符串,还有一大堆列表.

Pshaw! 你说,这与JSX或TSX中的“hello world”没有明显区别:

函数组件(){
  return (
    
“你好,世界!"
); }

然而,即使从这个基本的例子中,我们也可以发现一些关键的差异:

  • There are no embedded languages; everything within the ClojureScript example is either a string, a keyword, or a list.
  • It is concise; lists provide all of the expressivity we need without the redundancy of HTML closing tags.

这两个小小的差异会产生巨大的影响,不仅仅是在你如何 write 不仅在于你如何表达自己!

你会问,这是怎么回事? 让我们加入这场战斗,看看ClojureScript还为我们准备了什么……

构建块

在本ClojureScript教程中, 我将尽量不深入挖掘Clojure(脚本)的伟大之处(这是很多东西), 但我离题了). Nonetheless, 涵盖一些基本概念将是有用的,这样就有可能掌握我们在这里可以做的事情的广度.

对于那些有经验的Clojuristas和Lispians,请随意跳转到下一节!

首先我需要介绍三个主要概念:

Keywords

Clojure(脚本)有一个叫做关键字的概念. 它位于常量字符串(比如Java)和键之间. They are 求值为自身的符号标识符.

例如,关键字 :cat 总是指 :cat 再也没有别的了. 就像在Java中你可能会说:

MY_KEY = " MY_KEY ";

// ...

myMap.把(MY_KEY,东西);

// ...

myMap.得到(MY_KEY);

在Clojure中,你只需要:

(关联my-map:my-key thing)
(my-map :my-key) ; equivalent to (:my-key my-map) ...又好又灵活!

Also note: 在Clojure中,映射既是键到值的集合,就像Java一样 HashMap)和访问其内容的功能. Neat!

Lists

Clojure(脚本)是一种Lisp方言,这意味着它非常强调列表. 正如我之前提到的,有两件事需要注意:

  1. [] 向量和an很像吗 ArrayList.
  2. () 序列是不是很像 LinkedList.

要在Clojure(脚本)中构建一个列表,你可以这样做:

[1 2 3 4]
“你好”“世界”)
["my" "list" "contains" 10 "things"] ; you can mix and match types 
                                     ; in Clojure lists!

对于序列,这有点不同:

'(1 2 3 4)
”(“你好”“世界”)

的将 ' 在下一节中解释.

Functions

最后是函数. Clojure(脚本)中的函数是一个 sequence 是打印出来的 没有前置 '. 该列表的第一个元素是函数本身,接下来的所有元素都是 arguments. For example:

(+ 1 2 3 4)               ; -> 10
(str "hello" " " "world") ; -> "hello world"

(println“嗨!")           ; prints "hi!对着控制台说
(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函数
  [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)

更仔细的检查

现在我们已经了解了基本知识, 让我们深入一点细节,看看这里发生了什么.

在ClojureScript中,React通常使用一个库来完成 Reagent. 试剂使用Hiccup及其语法来表示HTML. From Hiccup回购’s wiki:

“Hiccup将Clojure数据结构变成这样:”

[:a {:href "http://github ..com GitHub“}]

"变成像这样的HTML字符串:"

GitHub

简单地说, 第一个元素 列表的部分成为HTML元素类型,其余部分成为该元素的内容. 您还可以选择提供一个属性映射,然后将其附加到该元素.

元素可以简单地嵌套在父元素的列表中,从而相互嵌套! 这是一个最容易理解的例子:

[:div
  [:h1 "这是标题"]
  [:p "下一个元素是1 + 1"]
  [:p (+ 1 1)]]

请注意,我们可以将任何旧函数或通用Clojure语法放入结构中,而不必显式声明嵌入方法. 毕竟,这只是一个列表!

更好的是,这个在运行时的值是多少?

[:div
  [:h1 "这是标题"]
  [:p "下一个元素是1 + 1"]
  [:p 2]]

一个关键词和内容的列表! 没有有趣的类型,没有神奇的隐藏方法. 这只是一份简单的清单. 你可以随心所欲地拼接和摆弄这个列表——你看到的就是你得到的.

Hiccup做布局,Reagent做逻辑和事件处理, 我们最终得到了一个功能齐全的React环境.

一个更复杂的例子

好了,让我们用一些成分把这些联系起来. React(和Reagent)的神奇之处在于,您可以将视图和布局逻辑划分为模块, 然后您可以在整个应用程序中重用它们.

假设我们创建了一个简单的组件,它显示一个按钮和一些简单的逻辑:

; widget.cljs

(defn组件
  [polite?]
  [:div
   [:p (str "礼貌时请勿按按钮"? ", please."))]
   [:input {:输入“按钮”
            :重视“推我”
            :on-click #(js/alert) "What did I told you .?")}]])

关于命名的快速说明:Clojure中的模块通常是命名空间的,所以 widget.cljs 可以在名称空间下导入 widget. 这意味着顶层 component 函数将被访问 部件/组件. 我喜欢每个模块只有一个顶层组件, 但这是一种风格偏好—您可能更喜欢这样命名组件函数 polite-component or widget组件.

这个简单的组件为我们提供了一个可选的礼貌小部件. (当有礼貌? ", please.") 计算结果为 ", please." when polite? == true and to nil when it is false.

现在我们把它嵌入到 app.cljs:

(defn app
  []
  [:div
   [:h1 "欢迎来到我的app"]
   [小部件/组件正确]])

在这里,我们通过将小部件作为列表的第一项调用来将其嵌入到应用程序组件中——就像HTML关键字一样! 然后,我们可以将任何子元素或参数作为同一列表的其他元素提供给组件. 这里我们简单地通过 true,所以在我们的小部件中 polite? == true,因此我们得到了礼貌的版本.

如果我们现在评估我们的应用函数,我们将得到以下结果:

[:div
   [:h1 "欢迎来到我的app"]
   [部件/组件 true]] ; <- 部件/组件 would look more like a 
                            ;    function reference, but I have kept it 
                            ;    clean for legibility.

Note how 部件/组件 没有被评估过! (See 职能组 如果你感到困惑.)

DOM树中的组件只有在更新后才会被评估(并在幕后转换为真正的React对象), 它能让事情保持漂亮和简洁,并减少你在任何时候都必须处理的复杂性.

有关这一主题的更多细节可供感兴趣的人查阅 在试剂文档中.

列表一直向下

Furthermore, 注意DOM只是一个列表的列表, 组件就是返回列表的列表的函数. 为什么这对学习ClojureScript如此重要?

Because 你可以对函数或列表做的任何事情,你也可以对组件做.

从这里开始,通过使用ClojureScript等Lisp方言可以获得复合回报:组件和HTML元素成为可以像操作任何其他普通数据一样操作的一等对象! 让我再说一遍:

组件和HTML元素是Clojure语言中支持的第一类对象!

没错,你没听错. 这几乎就像lisp被设计用来处理列表一样(提示:它们确实是).)

这包括:

  • 映射:对一个编号列表的元素的映射:
(def["绿色" "鸡蛋"和"火腿"])

(defn li-shout
  [x]
  [:li (string/大写x)]

(concat [:ol] (map li-shout words)

; becomes

[:ol
 (李:“绿色”)
 (李:“鸡蛋”)
 [:li "AND"]
 [李:“火腿”]]
  • 包装组件:
; in widget.cljs

(defn greeting-component
  [name]
  [:div
   [:p (str "Hiya " name ")!")]])

; ...

(def shouty-greeting-component
  #(widget/greeting-component (string/大写%)))

(defn app
  []
  [:div
   [:h1 "My App"]
   [shouty-greeting-component "Luke"]]) ; <- will show Hiya LUKE!
  • 注入属性:
(def default-btn-attrs
  {:输入“按钮”
   :值“我是一个按钮”
   :类“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教程展示了什么?

  • 一切都被简化为最简单的功能——元素只是列表, 组件就是返回元素的函数.
  • 因为组件和元素是一级对象,所以我们可以这样写 more with less.

这两点非常适合Clojure和函数式编程的精神——代码是 data 被操纵,复杂性是通过连接不太复杂的部分而建立起来的. 我们将程序(本例中的网页)表示为数据(列表), functions, 并保持这种状态,直到最后一刻,当试剂接管并将其转化为React代码. 这使得我们的代码可重用, 最重要的是,非常容易阅读和理解,几乎没有魔法.

越来越时尚

现在我们知道了如何制作具有一些基本功能的应用程序, 所以让我们继续讨论如何让它看起来更好. 有几种方法可以解决这个问题, 最简单的是使用样式表并在组件中引用它们的类:

.my-class {
  color: red;
}
[:div {:class "my-class"}
  “你好,世界!"]

正如您所期望的那样,它将向我们呈现一个美丽的红色“Hello, world”!” text.

However, 为什么要在组件中配置视图和逻辑代码时遇到这么多麻烦呢, 但是,然后将样式分离到样式表中—现在不仅要在两个不同的地方查找, 但你也在处理两种不同的语言!

为什么不把我们的CSS写成 组件中的代码 (在这里看到一个主题?). 这将给我们带来很多好处:

  • 所有定义组件的东西都在同一个地方.
  • 通过巧妙的生成,可以保证类名的唯一性.
  • CSS可以是动态的,随着数据的变化而变化.

我个人最喜欢的代码内css风格是with Clojure样式表(cljss). 嵌入的CSS如下所示:

;; -- STYLES ------------------------------------------------------------

(defstyles component-style []
  {:颜色“红色”
   宽度:“100%”})

;; -- VIEW --------------------------------------------------------------

(defn组件
  []
  [:div {:class (component style)}]
   “你好,世界!"])

defstyles 创建一个函数,它将为我们生成一个唯一的类名(这对任何导入我们组件的人来说都很好).)

class还可以为您做许多其他的事情(组合样式), animations, 元素覆盖, etc.),我在这里就不详述了. 我建议你自己去看看!

组装一个ClojureScript应用

最后,需要胶水把所有这些粘在一起. 幸运的是,除了一个项目文件和一个 index.html在这里,样板文件是最少的.

You need:

  • 项目定义文件, project.clj. 这是任何Clojure项目的主要内容,它定义了您的依赖项甚至直接从GitHub-和其他构建属性(类似于 build.gradle or package.json.)
  • An index.html 作为Reagent应用程序的绑定点.
  • 一些用于开发环境的设置代码,最后用于启动您的Reagent应用程序.

您可以在本ClojureScript教程中找到完整的代码示例 可以在GitHub上找到.

所以这就是它(目前). Hopefully, 我至少激起了你一点点的好奇心, 无论是检查Lisp方言(Clojure(脚本)或其他),还是尝试制作自己的Reagent应用程序! 我保证你不会后悔的.

和我一起跟进这篇文章, 进入一个州的状态管理 re-frame-向ClojureScript中的Redux问好!

了解基本知识

  • React是前端还是后端?

    React是JavaScript的前端开发框架.

  • 什么是ClojureScript?

    ClojureScript是一个针对JavaScript的Clojure编译器. 它发出的JavaScript代码与Google Closure优化编译器的高级编译模式兼容. 它继承了Clojure的大部分属性, 一种支持交互式开发的动态编程语言.

  • Lisp是用什么写的?

    Lisp是一组具有共同特征的语言(如Clojure)的名称, Common Lisp, Scheme etc.),所以“Lisp”本身并不是真正写出来的. (这就像问“蛋糕的原料是什么?“大多数蛋糕都遵循类似的主题,但会有不同的成分.)

  • Lisp好吗?

    Yes! Lisp优先考虑“代码即数据”的原则.这可以从名称本身的派生- list Processor中看出. Lisp方言中的一切都是数据——甚至函数调用也是一个参数列表! Lisp是一种奇妙的编程语言风格——有许多不同的风格可以满足任何需求.

  • Lisp是函数式编程吗?

    Lisp方言通常调用函数式和命令式编程语言的一部分. Clojure和ClojureScript是多范式的——它们严重依赖于函数式编程, 并且允许你在需要的时候分支成命令式. 这可以让你体验到两全其美!

  • 什么是谷歌闭包库?

    Google Closure Library是一个广泛且完善的JavaScript跨浏览器库, 包含各种UI工具, DOM操作, 服务器通信, testing, etc. 它旨在与Google闭包编译器一起使用.

  • 什么是谷歌闭包编译器?

    谷歌闭包编译器通过操作将JavaScript编译成“更好的JavaScript”, optimizing, 最小化以确保它下载和运行得更快. 此外,它还对代码运行一套检查,以尽量减少运行时出现的问题.

聘请Toptal这方面的专家.
Hire Now

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

订阅意味着同意我们的 隐私政策

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

订阅意味着同意我们的 隐私政策

Toptal开发者

加入总冠军® community.