Некоторое время назад я начал играть с ClojureScript и пытался заставить его работать с популярными фреймворками. Я играл с некоторыми из них, совсем недавно с Knockout.js. Этот пост подводит итог этих усилий и моего не очень оптимистичного взгляда на ClojureScript.
- Я попробовал «голый» jQuery . Это было довольно гладко.
- Я пробовал Backbone.js . Я заставил его работать на простом примере, хотя один читатель в Twitter справедливо заметил, что ClojureScript был отвратительным. Да, этот пример Backbone отвратителен. Позже я попытался сделать что-то менее тривиальное. В конце концов я в ужасе убежал, из-за несоответствия импеданса между сильно OO Backbone и не-OO ClojureScript под влиянием моего невежества в CLJS (и Backbone).
- Я также дал Angular.js шанс. Это началось действительно гладко, потому что Angular гордо заявляет, что он не столько полагается на объектно-ориентированное программирование. Это было здорово. В тот самый момент, когда я начал спорить с компилятором, переименовавшим мои переменные, вскоре последовало открытие, что Angular и Closure не нужны
Итак, пришло время для другого эксперимента — на этот раз Knockout.js. Я следовал официальному учебнику и вот что я в итоге придумал.
Страница
Полная страница в Hiccup выглядит следующим образом. Ничего особенно захватывающего здесь.
(defn render-body [] (hp/html5 [:head] [:body [:p "First name: " [:strong {:data-bind "text: firstName"} "todo"]] [:p "Last name: " [:strong {:data-bind "text: lastName"} "todo"]] [:p "Full name: " [:strong {:data-bind "text: fullName"} "todo"]] [:p "First name: " [:input {:data-bind "value: firstName"}]] [:p "Last name: " [:input {:data-bind "value: lastName"}]] [:button {:data-bind "click: capitalizeLastName"} "Go caps"] (hp/include-js "//ajax.aspnetcdn.com/ajax/knockout/knockout-2.1.0.js" "js/cljs.js") (hp/include-css "css/todo.css") ]))
ClojureScript
Самая интересная часть — это код ClojureScript. Вот один из способов сделать это:
(ns hello-clojurescript) (defn app-view-model [] (this-as this (set! (.-firstName this) (.observable js/ko "Bert")) (set! (.-lastName this) (.observable js/ko "Bertington")) (set! (.-fullName this) (.computed js/ko (fn [] (str (.firstName this) " " (.lastName this))) this)) (set! (.-capitalizeLastName this) (fn [] (.lastName this (-> this .lastName .toUpperCase))))) nil ) (.applyBindings js/ko (app-view-model.))
Да, мне нужно явно вернуть «ноль» там. В противном случае он возвращается this.fullName = ...
, и это нарушает нокаут.
Это работает, но трудно защитить его по сравнению с эквивалентом JS:
function AppViewModel() { this.firstName = ko.observable("Bert"); this.lastName = ko.observable("Bertington"); this.fullName = ko.computed(function() { return this.firstName() + " " + this.lastName(); }, this); this.capitalizeLastName = function() { var currentVal = this.lastName(); this.lastName(currentVal.toUpperCase()); }; } ko.applyBindings(new AppViewModel());
Полный код можно найти в моем репозитории GitHub .
Лучший путь — Макро
Этот код можно сделать намного лучше с помощью пользовательского макроса, который представлен в StackOverflow :
(defvar name_model first_name (observable "My") last_name (observable "Name") name (computed (fn [] (str (. this first_name) " " (. this last_name))))) (. js/ko (applyBindings name_model));
Теперь это будет что-то!
… За исключением того, что определение макросов в ClojureScript сложнее, чем в простом Clojure, до такой степени, что я еще не заставил его работать.
Выводы по ClojureScript
Я потратил довольно много часов на изучение ClojureScript, и у меня смешанные чувства.
- Довольно сложно заставить ClojureScript работать с существующими JS-фреймворками, в основном потому, что использование объектов в CLJS требует много церемоний.
- В отличие от простого Clojure, «веселье» и «продуктивность» — это не те слова, которые приходят мне на ум, когда я думаю о своих приключениях в CLJS. «Разочарование» и «запугивание» гораздо более уместны. Я постоянно спорю с компилятором и пытаюсь победить его, чтобы делать правильные вещи, не получая удовольствия от решения проблем.
- Некоторые вещи могут быть покрыты макросами, но в целом это выглядит очень … жестким и сдержанным. Я чувствую, что время от времени я вынужден попадать в другой трудный угол, тратить на него слишком много времени, писать другой макрос и так далее. Все это только для того, чтобы устранить пробелы и сделать ClojureScript похожим на… JavaScript. Фактически это похоже на написание собственного слоя макросов для компиляции JS-like-DSL в ClojureScript .
- Возможно, есть лучший способ, и я просто делаю что-то не так. Может быть, вы вообще не должны использовать эти фреймворки, но катите свои или используйте те немногие, которые написаны на ClojureScript?
- Возможно, все это имеет мало смысла в таком маленьком масштабе, и вам нужно что-то действительно большое, чтобы оценить ClojureScript. Вам просто нужно потратить много часов на прохождение кривой обучения и макроса своего выхода. Может быть, тогда она станет более продуктивной, модульной и еще много чего. Я не знаю, на самом деле у меня слишком мало опыта в самом JavaScript, чтобы отвечать на такие вопросы. Я не очень оптимистично по этому поводу, хотя.