Когда я писал свой последний пост на ClojureScript , я действительно надеялся, что кто-нибудь заскочит и скажет: «Вы делаете это неправильно! Вот как.»
Я получил несколько интересных ответов, особенно на HackerNews (где этот пост был кратко на первой странице). Кажется, что здесь действительно два лагеря: новички в таком же замешательстве, как и я, и профессионалы, которые говорят, что вам просто нужно потратить время и учиться, тогда вы сможете эффективно использовать некоторые из существующих JS-сред или (лучше?) сверните свои собственные рамки ClojureScript. Говорят, это того стоит, когда ваша кодовая база станет достаточно большой.
Получение углов на работу
Во всяком случае, Грег Вебер здесь, в моем блоге, отметил, что вы можете использовать Angular с Closure — просто нужно использовать явное внедрение зависимостей. До сих пор Angular, казалось, требовал наименьшей работы с CLJS, поэтому я был рад дать ему еще один шанс. Я также нашел эту заметку по минификации в англоязычных документах очень полезной.
В конце я успешно переписал пример приложения «todo». Вот один из способов сделать это:
(defn add-todo [scope] (fn [] (.push (.-todos scope) (js-obj "text" (.-todoText scope) "done" false)) (aset scope "todoText" ""))) (defn remaining [scope] (fn [] (count (filter #(not (.-done %)) (.-todos scope))))) (defn archive [scope] (fn [] (let [arr (into-array (filter #(not (.-done %)) (.-todos scope)))] (aset scope "todos" arr )))) (defn CTodoCtrl [$scope] (def $scope.todos (array (js-obj "text" "learn angular" "done" true))) (def $scope.addTodo (add-todo $scope)) (def $scope.remaining (remaining $scope)) (def $scope.archive (archive $scope))) (def TodoCtrl (array "$scope" CTodoCtrl))
Последние 4 строки эквивалентны использованию этого синтаксиса массива в JavaScript:
TodoCtrl = ['$scope', CTodoCtrl];
Еще один способ сделать это — установить $inject
свойство следующим образом:
(def TodoCtrl CTodoCtrl) (aset TodoCtrl "$inject" (array "$scope"))
Как обычно, полный рабочий проект можно найти в моем репозитории GitHub .
Детали реализации
Определение функции
В приведенном выше примере я определяю функции CTodoCtrl
с помощью «заводских функций». Я нахожу это немного более читабельным, но это также может быть сделано с помощью определения на месте, например:
(aset $scope "remaining" (fn [] (count (filter #(not (.-done %)) (.-todos $scope)))))
К сожалению, я не смог заставить его работать с анонимными функциями (он был скомпилирован CTodoCtrl.remaining = (function CTodoCtrl.remaining() {...
):
(aset $scope "remaining" #(...))
Это тоже не сработало (хотелось бы!)
(defn $scope.remaining [] (...))
Объекты, Массивы
Я не совсем доволен использованием объектов здесь — я определенно предпочел бы использовать карты Clojure следующим образом:
; Instead of: ; (def $scope.todos (array (js-obj "text" "learn angular" "done" true))) ; Do: (def $scope.todos [{:text "learn angular" :done true}])
; Insetad of: ; (into-array (filter #(not (.-done %)) (.-todos scope))) ; Do: (filter #(not (:done %)) (:todos scope))
К сожалению, похоже, что Angular не любит типы ClojureScript и наоборот. Выглядит как небольшое, исправимое раздражение.
ClojureScript!
Это все еще уродливо в некоторых местах и не очень впечатляюще, но мне нравится использовать функциональное программирование с ClojureScript вместо циклов JavaScript.
Я имею в виду заменить это:
var count = 0; angular.forEach($scope.todos, function(todo) { count += todo.done ? 0 : 1; }); return count;
с участием:
(count (filter #(not (.-done %)) (.-todos scope)))
И это:
var oldTodos = $scope.todos; $scope.todos = []; angular.forEach(oldTodos, function(todo) { if (!todo.done) $scope.todos.push(todo); });
с участием:
(let [arr (into-array (filter #(not (.-done %)) (.-todos scope)))] (aset scope "todos" arr))
решение суда
В общем, я, наконец, вижу свет в конце туннеля. Интеграция с Angular выглядит очень многообещающе, после устранения небольших проблем взаимодействия с отображением типов она может быть довольно выразительной и простой. Я, вероятно, пока отложу Knockout и исследую Angular.