Как обрабатывать элементы ввода в Clojure Re-Frame?

У меня есть пара вариантов, но оба кажутся немного медленными, и я думаю, что должна быть лучшая альтернатива. Я просто хотел бы иметь возможность создавать формы, даже динамически создавать их (например, добавлять строки в форму из моего приложения) и иметь доступ, соответствующий реагенту/рефрейму/реагированию, к значениям различных входов.

Не уверен, что любой из них является лучшей альтернативой, поскольку они оба запускают функции после каждого :on-change...

Вариант 1 — обновить :on-change в глобальном атоме

[:input {:value       @new-job-form
         :on-change   #(dispatch [:new-job-form (-> % .-target .-value)])}]

(reg-event-db
 :new-job-form
 (fn [db [_ v]]
   (assoc db :new-job-form v)))

Вариант 2 — обновить некоторое локальное состояние, которое отправляет только глобальный атом :on-blur

(defn text-input
  "adapted from:
  https://yogthos.net/posts/2016-09-25-ReagentComponents.html

  The big idea is this holds local state, and pushes it to the global
  state only when necessary"
  [{:keys [sub-path disp]}]
  (r/with-let [value    (r/atom nil)
               focused? (r/atom false)]
    [:div
     [:input
      {:type      :text
       :on-focus  #(do (reset! value @(subscribe sub-path))
                       (reset! focused? true))
       :on-blur   #(do (dispatch (conj disp @value))
                       (reset! focused? false))
       :value     (if @focused? @value @(subscribe sub-path))
       :on-change #(reset! value (-> % .-target .-value))}]]))

Второй вариант немного менее запаздывает, но более запаздывает, чем просто ввод необработанного текста...

РЕДАКТИРОВАТЬ:

Вариант 3 – для полноты картины несколько отличается от TODOMVC от re-frame.

(defn text-input
  "adapted from re-frame's TODOMVC:
      https://github.com/Day8/re-frame/blob/master/examples/todomvc/src/todomvc/views.cljs

  note: this is one-way bound to the global atom, it doesn't subscribe to it"
  [{:keys [on-save on-stop props]}]
  (let [inner (r/atom "")]
    (fn [] [:input (merge props
                          {:type        "text"
                           :value       @inner
                           :on-blur     (on-save @inner)
                           :on-change   #(reset! inner (-> % .-target .-value))
                           :on-key-down #(case (.-which %)
                                           13 (on-save @inner) ; enter
                                           27 (on-stop) ; esc
                                           nil)})])))

[text-input {:on-save #(dispatch [:new-job-form {:path [:a]
                                                         :v    %}])
                     :on-stop #(js/console.log "stopp")
                     :props   {:placeholder "url"}}]

person Josh.F    schedule 14.10.2016    source источник
comment
Что вы имеете в виду под лаги? Вы действительно испытываете какое-либо видимое падение производительности? Я многого добился, используя любой из двух описанных вами методов, и у меня никогда не было проблем с производительностью.   -  person Kuba Birecki    schedule 14.10.2016
comment
@KubaBirecki да, производительность заметно падает, поскольку текстовое поле изо всех сил пытается не отставать от моего набора текста. У меня быстрый компьютер, другой очевидной причины, по которой он должен работать медленно, нет.   -  person Josh.F    schedule 14.10.2016
comment
Я не совсем понимаю, каковы ваши требования, но я бы начал с просмотра re-frame todomvc и того, как это добавляет todos, и посмотреть, поможет ли это вам начать: github.com/Day8/re-frame/tree/master/examples/todomvc   -  person Walton Hoops    schedule 14.10.2016


Ответы (1)


Рефрейминг и reagent+ React на более низком уровне, попытайтесь ограничить повторный рендеринг компонентами, которые изменяются. В вашем случае задержка может возникнуть, если другой компонент (или весь пользовательский интерфейс) повторно отображается в дополнение к текстовому полю, которое было единственным, что изменилось.

Пример построения на вашем "Вариант один":

(defn busy-wait [ms]
  (let [start (.getTime (js/Date.))]
    (while (< (.getTime (js/Date.)) (+ start ms)))))

(defn slow-component []
  (busy-wait 2000)
  (.log js/console "Ouch!")
  [:h2 "I was busy"])

(defn main-panel []
  (let [new-job-form (re-frame/subscribe [:new-job-form])
    (fn []
      [:div.container-fluid
        (slow-component)
        [:input       {:value    @new-job-form
         :on-change   #(dispatch [:new-job-form (-> % .-target .-value)])}]
;; etc

Это приводит к повторному рендерингу slow-component каждый раз, когда вводится текст, и действительно запаздывает, поскольку slow-component занимает не менее 2000 мс для рендеринга.

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

[:div.container-fluid
  [slow-component]

Это позволяет повторно кадрировать, чтобы увидеть, что slow-component не нуждается в повторном рендеринге, так как его данные не изменились. Мы пропускаем это рассуждение, когда сами вызываем функцию в исходном примере:

[:div.container-fluid
  (slow-component)

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

person Toni Vanhala    schedule 01.07.2017