Ратом, указанный в компоненте, не может обновить этот компонент?

Только начинаю с Реагента. У меня есть кнопка, значение которой :on-click вызывает выполнение функции с интенсивным использованием ЦП; возвращение занимает много времени. Я хочу обновить текст самой кнопки, чтобы уведомить пользователя о том, что, возможно, придется подождать, поэтому я определяю ратом, который будет указывать текст кнопки, а затем я reset! ратом во время работы функции.

Это работает, если я определяю ратом вне моей функции компонента кнопки, но не работает, если я определяю ратом внутри функции компонента через let или если я reset! ратом на верхнем уровне в функции компонента. То есть текст кнопки не изменится, если я раскомментирую первую закомментированную строку ниже или раскомментирую две строки для let. Я делаю что-то неправильно? Это ожидаемое поведение? Какое общее правило относительно ratom и DOM-обновления применимо здесь?

(def label (reagent.core/atom "Make chart"))

(defn chart-button
  [normal-label running-label]
;  (reset! label normal-label)                   ; reset globally-defined ratom
;  (let [label (reagent.core/atom normal-label)] ; use local ratom
    [:button {:on-click (fn []
                          (reset! label running-label)
                          (js/setTimeout (fn []
                                            (cpu-intensive-function)
                                            (reset! label normal-label))
                                         10))
              }
     @label] ;)
)

...
[chart-button "make chart" "running..."]
...

(Не актуально, но для пояснения: я использую прием setTimeout, описанный здесь, чтобы заставить DOM обновляться, несмотря на то, что в противном случае длительно работающая функция не позволила бы браузеру обновлять DOM во время работы функции.)


person Mars    schedule 16.07.2016    source источник


Ответы (1)


Ваша проблема заключается в том, что ваша функция chart-button вызывается каждый раз, когда компонент необходимо (повторно) отобразить. Так, например, в вашем примере с локальным сбросом кто-то нажимает кнопку, и ваш label сбрасывается на running-label. Реагент обнаруживает это изменение и вызывает вашу функцию chart-button, чтобы увидеть, как должна выглядеть новая визуализированная кнопка, и в этот момент возвращаются ваши первые изменения сброса. В версиях let аналогичная проблема.

Есть несколько способов работы с локальным состоянием в Reagent. Самый простой способ — вернуть функцию из вашего компонента вместо вектора, как в этом примере.

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

По сути, в Reagent ваш компонент может быть либо функцией рендеринга, либо функцией, которая возвращает функцию рендеринга. В приведенном выше случае мы используем замыкание для установки некоторого локального состояния, а затем возвращаем функцию рендеринга, которая использует это состояние. Каждый раз, когда seconds-elapsed увеличивается, внутренняя функция вызывается снова, и компонент перерисовывается.

Другой способ более сложен, но может помочь вам разобраться в этом. Вы можете получить полный контроль над жизненным циклом компонента, используя create-class вместо использования функций в качестве компонентов.

person Walton Hoops    schedule 17.07.2016
comment
Большое спасибо, очень четкий диагноз и объяснение. Все это имеет смысл сейчас. (Возможно, я должен был знать. Я прочитал Создание компонентов реагентов, но он не проникся.) - person Mars; 18.07.2016