Реагент: Минималистичный React Для Clojurescript

Хабр, здравствуйте.

Я премьер-министр, который занимается вещами, которые могут его съесть.

Итак, я перевел часть документации по Реагенту, если будет полезно, выложу продолжение.



Реагент: Минималистичный React для ClojureScript

В мире кодирования, где нет никаких ограничений, цель оправдывает крайности.



Введение в реагент

Реагент обеспечивает минималистическое взаимодействие между ClojureScript И Реагировать .

Он позволяет создавать эффективные компоненты React, используя только простые функции ClojureScript и данные, описывающие пользовательский интерфейс с помощью икота -похожий синтаксис.

Цель Reagent — сделать возможным создание сколь угодно сложных пользовательских интерфейсов, используя пару основных концепций, и при этом по умолчанию быть достаточно быстрым, чтобы вам редко приходилось думать о производительности.

Самый простой компонент реагента может выглядеть примерно так:

  
  
  
  
  
  
  
  
  
  
   

(defn simple-component [] [:div [:p "I am a component!"] [:p.someclass "I have " [:strong "bold"] [:span {:style {:color "red"}} " and red "] "text."]])



Реагент: Минималистичный React для ClojureScript

Вы можете создавать новые компоненты, вставляя внутрь другие компоненты.

Как в этом примере:

(defn simple-parent [] [:div [:p "I include simple-component."] [simple-component]])



Реагент: Минималистичный React для ClojureScript

Данные передаются дочерним компонентам с использованием простых типов данных Clojure. Как в этом примере:

(defn hello-component [name] [:p "Hello, " name "!"]) (defn say-hello [] [hello-component "world"])



Реагент: Минималистичный React для ClojureScript

Примечание : В приведенном выше примере hello-comment можно было бы так же легко вызвать как обычную функцию Clojure, а не как компонент Reagent, т. е.

используя круглые скобки вместо квадратных.

Единственная разница будет заключаться в производительности, поскольку «настоящие» компоненты реагента перерисовываются только тогда, когда их данные изменяются.

Однако более сложные компоненты (см.

ниже) необходимо вызывать в квадратных скобках.

Вот еще один пример, который отображает список элементов:

(defn lister [items] [:ul (for [item items] ^{:key item} [:li "Item " item])]) (defn lister-user [] [:div "Here is a list:" [lister (range 3)]])



Реагент: Минималистичный React для ClojureScript

Примечание.

Вышеупомянутая часть ^{: key item} на самом деле не обязательна в этом простом примере, но добавление уникального ключа к каждому элементу в динамически генерируемом списке компонентов является хорошей практикой и повышает производительность отрисовки больших списков.

Ключ можно указать либо (как в этом примере) как метаданные, либо как элемент :key в первом аргументе компонента (если это карта).

Дополнительную информацию смотрите в документации React.

Управление состоянием в React

Самый простой способ управлять состоянием — использовать собственную версию атома Reagent. Он работает точно так же, как и в clojure.core, за исключением того, что он разыменовывается/разыменовывается каждый раз при использовании ( отменять определение ).

Любой компонент, использующий атом, автоматически перерисовывается при изменении его значения.

Продемонстрируем это на простом примере:

(ns example (:require [reagent.core :as r])) (def click-count (r/atom 0)) (defn counting-component [] [:div "The atom " [:code "click-count"] " has value: " @click-count ".

" [:input {:type "button" :value "Click me!" :on-click #(swap! click-count inc)}]])



Реагент: Минималистичный React для ClojureScript

Иногда вам может потребоваться использовать локальное состояние в компоненте.

Это легко сделать с атомом.

Мы вызываем setTimeout, и каждый раз компонент перерисовывается с обновленным счетчиком: Вот пример

(defn timer-component [] (let [seconds-elapsed (r/atom 0)] (fn [] (js/setTimeout #(swap! seconds-elapsed inc) 1000) [:div "Seconds Elapsed: " @seconds-elapsed])))



Реагент: Минималистичный React для ClojureScript

В предыдущем примере также используется еще одна особенность Reagent: функция компонента может возвращать другую функцию, которая используется для фактического рендеринга.

Эта функция вызывается с теми же аргументами, что и первая.

Это позволяет вам выполнять некоторую настройку вновь созданных компонентов без необходимости иметь дело с событиями жизненного цикла реагента.

Просто передав атом, вы можете разделить управление состоянием между компонентами.

Как это сделано:

(ns example (:require [reagent.core :as r])) (defn atom-input [value] [:input {:type "text" :value @value :on-change #(reset! value (-> % .

-target .

-value))}]) (defn shared-state [] (let [val (r/atom "foo")] (fn [] [:div [:p "The value is now: " @val] [:p "Change it here: " [atom-input val]]])))



Реагент: Минималистичный React для ClojureScript

Комментарий : Функции компонентов можно вызывать с любыми аргументами, если они неизменяемы.

Вы можете использовать изменяемые структуры, но тогда вам нужно будет убедиться, что компонент выполняет повторную визуализацию при изменении данных.

По умолчанию Reagent предполагает, что два объекта равны, если они являются одним и тем же объектом.



Основной API

Reagent поддерживает большинство API-интерфейсов React, но на самом деле для большинства приложений необходима только одна точка входа: React.dom/render. Требуется два аргумента: компонент и узел DOM. Например, код самого первого примера на странице будет выглядеть так:

(ns example (:require [reagent.core :as r])) (defn simple-component [] [:div [:p "I am a component!"] [:p.someclass "I have " [:strong "bold"] [:span {:style {:color "red"}} " and red "] "text."]]) (defn render-simple [] (rdom/render [simple-component] (.

-body js/document)))



Собираем все это вместе

Вот более практичный пример: простой калькулятор ИМТ.

Данные хранятся в одной карте реагента.

core/atom: со следующими ключами: рост, вес и ИМТ.



(ns example (:require [reagent.core :as r])) (defn calc-bmi [{:keys [height weight bmi] :as data}] (let [h (/ height 100)] (if (nil? bmi) (assoc data :bmi (/ weight (* h h))) (assoc data :weight (* bmi h h))))) (def bmi-data (r/atom (calc-bmi {:height 180 :weight 80}))) (defn slider [param value min max invalidates] [:input {:type "range" :value value :min min :max max :style {:width "100%"} :on-change (fn [e] (let [new-value (js/parseInt (.

e -target -value))] (swap! bmi-data (fn [data] (-> data (assoc param new-value) (dissoc invalidates) calc-bmi)))))}]) (defn bmi-component [] (let [{:keys [weight height bmi]} @bmi-data [color diagnose] (cond (< bmi 18.5) ["orange" "underweight"] (< bmi 25) ["inherit" "normal"] (< bmi 30) ["orange" "overweight"] :else ["red" "obese"])] [:div [:h3 "BMI calculator"] [:div "Height: " (int height) "cm" [slider :height height 100 220 :bmi]] [:div "Weight: " (int weight) "kg" [slider :weight weight 30 150 :bmi]] [:div "BMI: " (int bmi) " " [:span {:style {:color color}} diagnose] [slider :bmi bmi 10 50 :weight]]]))



Реагент: Минималистичный React для ClojureScript



Производительность

React очень быстрый, как и React. Фактически, Reagent почти всегда будет даже быстрее, чем обычный React, благодаря оптимизации, выполненной ClojureScript. Подключенные компоненты перерисовываются только тогда, когда их параметры изменились.

Перерисовка может быть вызвана изменением значения атома, передачи аргументов компоненту или состояния компонента.

Все параметры сравниваются по одинаковым? функция, которая сравнивает указатели и поэтому не влияет на производительность.

Карты, передаваемые в качестве аргументов компонентам, сравниваются одинаково: они считаются идентичными, если все их элементы идентичны.

Это также относится ко встроенным компонентам React, таким как :div, :p и т. д. Все это означает, что большую часть времени вам просто не придется беспокоиться о производительности.

Просто определите свой пользовательский интерфейс так, как хотите, и он будет достаточно быстрым.

Есть несколько ситуаций, о которых вам, возможно, придется позаботиться.

Если вы дадите Reagent большой список компонентов для рендеринга, вам, возможно, придется присвоить им всем уникальный атрибут :key, чтобы ускорить рендеринг (см.

выше).

Также обратите внимание, что анонимные функции обычно не равны друг другу, даже если они представляют один и тот же код и замыкание.

Но опять же, в целом вам просто нужно верить, что React и Reagent будут достаточно быстрыми.

Точно эта страница состоит из одного компонента Reagent с тысячами дочерних компонентов (каждая скобка и т. д. в примерах кода является отдельным компонентом), и при этом страница может обновляться несколько раз в секунду без загрузки браузера.

Кстати, эта страница также использует еще один трюк React: вся страница предварительно визуализируется с использованием Nodejs и React.dom.server/render-to-string. При загрузке в браузер React автоматически добавляет обработчики событий в уже существующее дерево DOM.

Полная демо-версия

Reagent поставляется с парой полных примеров, файлами проекта Leiningen и всем остальным.

Вот один из них в действии:

(ns simpleexample.core (:require [reagent.core :as r] [reagent.dom :as rdom] [clojure.string :as str])) (defonce timer (r/atom (js/Date.))) (defonce time-color (r/atom "#f34")) (defonce time-updater (js/setInterval #(reset! timer (js/Date.)) 1000)) (defn greeting [message] [:h1 message]) (defn clock [] (let [time-str (-> @timer .

toTimeString (str/split " ") first)] [:div.example-clock {:style {:color @time-color}} time-str])) (defn color-input [] [:div.color-input "Time color: " [:input {:type "text" :value @time-color :on-change #(reset! time-color (-> % .

-target .

-value))}]]) (defn simple-example [] [:div [greeting "Hello world, it is now"] [clock] [color-input]]) (defn ^:export run [] (rdom/render [simple-example] (js/document.getElementById "app")))



Реагент: Минималистичный React для ClojureScript



Тодомвц

Список дел в Reagent (за исключением маршрутизации и сохранения данных) выглядит примерно так:

(ns todomvc.core (:require [reagent.core :as r] [reagent.dom :as rdom] [clojure.string :as str])) (defonce todos (r/atom (sorted-map))) (defonce counter (r/atom 0)) (defn add-todo [text] (let [id (swap! counter inc)] (swap! todos assoc id {:id id :title text :done false}))) (defn toggle [id] (swap! todos update-in [id :done] not)) (defn save [id title] (swap! todos assoc-in [id :title] title)) (defn delete [id] (swap! todos dissoc id)) (defn mmap [m f a] (->> m (f a) (into (empty m)))) (defn complete-all [v] (swap! todos mmap map #(assoc-in % [1 :done] v))) (defn clear-done [] (swap! todos mmap remove #(get-in % [1 :done]))) (defonce init (do (add-todo "Rename Cloact to Reagent") (add-todo "Add undo demo") (add-todo "Make all rendering async") (add-todo "Allow any arguments to component functions") (complete-all true))) (defn todo-input [{:keys [title on-save on-stop]}] (let [val (r/atom title) stop #(do (reset! val "") (if on-stop (on-stop))) save #(let [v (-> @val str str/trim)] (if-not (empty? v) (on-save v)) (stop))] (fn [{:keys [id class placeholder]}] [:input {:type "text" :value @val :id id :class class :placeholder placeholder :on-blur save :on-change #(reset! val (-> % .

-target .

-value)) :on-key-down #(case (.

-which %) 13 (save) 27 (stop) nil)}]))) (def todo-edit (with-meta todo-input {:component-did-mount #(.

focus (rdom/dom-node %))})) (defn todo-stats [{:keys [filt active done]}] (let [props-for (fn [name] {:class (if (= name @filt) "selected") :on-click #(reset! filt name)})] [:div [:span#todo-count [:strong active] " " (case active 1 "item" "items") " left"] [:ul#filters [:li [:a (props-for :all) "All"]] [:li [:a (props-for :active) "Active"]] [:li [:a (props-for :done) "Completed"]]] (when (pos? done) [:button#clear-completed {:on-click clear-done} "Clear completed " done])])) (defn todo-item [] (let [editing (r/atom false)] (fn [{:keys [id done title]}] [:li {:class (str (if done "completed ") (if @editing "editing"))} [:div.view [:input.toggle {:type "checkbox" :checked done :on-change #(toggle id)}] [:label {:on-double-click #(reset! editing true)} title] [:button.destroy {:on-click #(delete id)}]] (when @editing [todo-edit {:class "edit" :title title :on-save #(save id %) :on-stop #(reset! editing false)}])]))) (defn todo-app [props] (let [filt (r/atom :all)] (fn [] (let [items (vals @todos) done (->> items (filter :done) count) active (- (count items) done)] [:div [:section#todoapp [:header#header [:h1 "todos"] [todo-input {:id "new-todo" :placeholder "What needs to be done?" :on-save add-todo}]] (when (-> items count pos?) [:div [:section#main [:input#toggle-all {:type "checkbox" :checked (zero? active) :on-change #(complete-all (pos? active))}] [:label {:for "toggle-all"} "Mark all as complete"] [:ul#todo-list (for [todo (filter (case @filt :active (complement :done) :done :done :all identity) items)] ^{:key (:id todo)} [todo-item todo])]] [:footer#footer [todo-stats {:active active :done done :filt filt}]]])] [:footer#info [:p "Double-click to edit a todo"]]])))) (defn ^:export run [] (rdom/render [todo-app] (js/document.getElementById "app")))



Реагент: Минималистичный React для ClojureScript

Благодарность Спасибо за помощь в переводе зомбиQWERTY .

В опросе могут участвовать только зарегистрированные пользователи.

Войти , Пожалуйста.

Был ли этот перевод полезен для вас? 94,12% Да, выложи остальное 16 5,88% Нет, не лезь в программирование, оно тебя сожрет 1 0% Что я только что прочитал? 0 проголосовали 17 пользователей.

7 пользователей воздержались.

Теги: #программирование #Процесс обучения в IT #react.js #react #clojure #реагент #обучение программированию

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.