Fork me on GitHub
#clojure-russia
<
2017-01-08
>
seryh11:01:02

если нет time/before? то всегда можно привести к unixtime и сравнить

kuzmin_m16:01:34

Привет! В react есть [“Higher-Order Components”](https://facebook.github.io/react/docs/higher-order-components.html). Есть что-то оподобное для reagent? Или нужно спускаться на уровень реакта, через reagent.core/create-class и reagent.core/reactify-component? Я не нашел в гугле ничего подходящего. Может быть у кого-то есть примеры из своих проектов?

dottedmag17:01:58

В реагенте компонент - это же просто функция, так что чем не хватает композиции функций?

dottedmag17:01:45

Или HOF: принять функцию, вернуть функцию.

kuzmin_m17:01:33

@dottedmag например, нужно через react context предать некую функцию, что бы она была у всех детей и детей детей. в том же redux сделано как: есть компонент Provider, который устанавливает контекст что бы не работать с контекстом напрямую используют HOС этот HOС работает с контекстом, а эту функцию предает через props обернутому компоненту

misha17:01:52

как-то сложна

kuzmin_m17:01:26

clojure

(def provider
  (r/create-class
   {:getChildContext
    (fn [] #js{:foo "bar"})

    :childContextTypes
    #js{:foo js/ui.React.PropTypes.any.isRequired}

    :reagent-render
    (fn [children]
      [:div children])}))

(defn wrapper [component]
  (r/create-class
   {:contextTypes
    #js{:foo js/ui.React.PropTypes.any.isRequired}

    :reagent-render
    (fn [& args]
      (let [this (r/current-component)
            foo (.. this -context -foo)]
        (into [component foo] args)))}))

(def consumer
  (wrapper (fn [foo buzz] [:div
                           [:div foo]
                           [:div buzz]])))



  (r/render-component [provider
                       [consumer "buzz value"]]
                     mount-point)

kuzmin_m17:01:50

это работает, но выглядит как-то страшно

misha17:01:35

зачем?

kuzmin_m17:01:05

> нужно через react context предать некую функцию, что бы она была у всех детей и детей детей.

misha17:01:40

и она динамическая?

misha17:01:48

чем-то запахло

kuzmin_m17:01:26

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

kuzmin_m17:01:50

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

niquola17:01:41

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

kuzmin_m18:01:51

Ок, пример с роутером немного не подходящий, т.к. есть всего лишь один объект history. Но предположим, что есть следующее: комонент Root хочет передавать своим детям на любую глубину какое-то значение, возможно значение из своего состояния компонент C хочет принимать это значение и у нас в есть следующая иерархия

<div>
  <Root param={1}>
    ....
    …<C />....
   </Root>
  <Root param={1}>
    ....
    …<C />....
   </Root>
</div>
когда у нас одновременно два Root, вариант с глобальным атомом не работает и нужен именно контекст.

kuzmin_m18:01:47

дабы не светить работу с контекстом наружу, применяют HOC, который берет работу с контекстом на себя, а оборачиваемому компоненту передает значение через props

niquola18:01:59

Сделай два ключа в хэшмапом, не мысли компонентами - функции и база данных, пусть дети сами лазают за тем, что им нужно

kuzmin_m18:01:40

как C узнает этот ключ?

kuzmin_m18:01:58

он там где то спрятан и у него нет параметров

kuzmin_m18:01:11

т.е. в реакте я могу это сделать

misha18:01:15

аргумент? 😄

kuzmin_m18:01:27

да нет у него аргумента

misha18:01:40

ты напиши-напиши фабриками, а завтра прочитать попробуй, что написал

dottedmag18:01:13

Что за ключ-то? Есть два варианта: либо это какой-то generic-компонент, типа из react-bootstrap, и тогда ему ключ должен подсунуть его родительский компонент, либо это какой-то частный компонент, и тогда он сам знает, что он рисует.

niquola18:01:29

Ты опиши конкретнее, что делаешь

kuzmin_m18:01:27

ок, я нашел в сорцах реагента что-то похожее, как напишу - отпишусь

dottedmag18:01:03

Курсор, что ли?

kuzmin_m18:01:53

собственно работа с HOC вот тут

:reagent-render
    (fn [& args]
      (let [this (r/current-component)
            foo (.. this -context -foo)]
        (into [component foo] args)))}))

kuzmin_m18:01:43

он берет компонент и вставляет певым аргументом нужное значение, за которое отвечает этот HOC

kuzmin_m18:01:07

если интересно посмотреть, то это из проекта https://github.com/darkleaf/quester

kuzmin_m18:01:56

спасибо за советы 😃 хоть и сам разобрался

airnsk19:01:50

@kuzmin_m все-таки не совсем понятно - для чего ты это делаешь ? протаскивать контекст - это же не самоцель. какая задача решается - можешь реальный пример привести?

kishanov19:01:05

В реакте это сделано, потому что там такой data flow через props вниз спускаются. Реагентовские приложения обычно этого не требуют. Собсно re-frame родился из-за того, что data flow надо было отделить от компонентов

kuzmin_m19:01:23

@kishanov а в re-frame есть dependency injection?

kuzmin_m19:01:21

ИМХО контекст это не про data flow, а как раз про компоненты

kishanov19:01:32

ну прям чтобы в концепции DI видимо нет, потому что это ОО паттерн 🙂

kuzmin_m19:01:49

я в кложе DI использую

kuzmin_m19:01:06

тот же component почти про DI

kuzmin_m19:01:16

не совсем DI, но что-то общее есть

kishanov19:01:11

короче, у меня есть приложения в котором компоненты вкладываются друг в друга как матрешка. Раньше мы делали такие higher order components (например, контейнер для rest запросов, который должен на component-did-mount подсосать данные, распарсить http запрос, и если все хорошо прокинуть данные дальше для вьюхи, которая это рендерит)

kishanov19:01:38

сейчас мы от этого уходим, потому что в какой-то момент количество данных, которые спускаются через “context” начинает обрастать

kishanov19:01:07

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

kishanov19:01:40

(defn testplan-details-container []
  (let [response (re-frame/subscribe [::subs/testplan-list-response])]
    (reagent/create-class
      { :component-will-mount
       #(re-frame/dispatch [::events/init-testplan-list-container])

       :component-function
       (fn []
         (cond
           (= :error (:failure @response))
           [re-com/alert-box
            :alert-type :danger
            :header "Server error"]

           (nil? @response)
           [re-com/throbber]

           :else
           [testplans-table]))})))

kishanov19:01:27

вот пример high level component’а в моем понимании

kishanov19:01:50

соответственно testplans-table внутри себя подпишется на данные нужные данные, потому что после этого оно гарантировано будет в “глобальном атоме"

kishanov19:01:56

как только разделяешь глобальный state приложения и вьюхи, вся эта черная магия с тем, как прогнуться в react’овкий data flow уходит, потому что сам можешь достать данные когда надо и какие надо

kishanov19:01:27

https://www.youtube.com/watch?v=5hGHdETNteE - Nolen рассказывает про это где-то на 17й минуте

kuzmin_m19:01:59

видимо что-то похожее и я использую

kuzmin_m19:01:21

читал статью про контейнеры и комопенты?

kuzmin_m19:01:29

комоненты - это просто верстка

kuzmin_m19:01:34

контейнеры только логика

kuzmin_m19:01:14

In an earlier version of this article I claimed that presentational components should only contain other presentational components. I no longer think this is the case. Whether a component is a presentational component or a container is its implementation detail. You should be able to replace a presentational component with a container without modifying any of the call sites. Therefore, both presentational and container components can contain other presentational or container components just fine.

kuzmin_m19:01:27

вот с этим я не согласен категорически

kuzmin_m19:01:43

презентационные не могу знать про контейнеры

kuzmin_m19:01:15

презентационные должны получать элементы через параметры

kuzmin_m19:01:25

а уже через параметры подставляются контейнеры

kuzmin_m19:01:12

а это реализация

kuzmin_m19:01:20

и тут есть работа с состоянием

kuzmin_m19:01:37

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

kuzmin_m19:01:07

фактически main_page.cljs - корень агрегации

kuzmin_m19:01:28

и тут как раз можно пробросить состояние от куда угодно

kuzmin_m19:01:20

@airnsk я опишу на конкретном примере 1) https://github.com/darkleaf/quester/blob/master/src/cljs/quester/web.cljs#L27 начинается все здесь. это quester.web, который использует quester.containers.history 2) https://github.com/darkleaf/quester/blob/master/src/cljs/quester/containers/history.cljs это компонент истории, который управляет html5 history он использует quester.routes.web 3) https://github.com/darkleaf/quester/blob/master/src/cljc/quester/routes/web.cljc это описание роутинга. он использует контроллер 4) https://github.com/darkleaf/quester/blob/master/src/cljs/quester/controllers/web/site.cljs это "контроллер" который возвращает комонент 5) компонент хочет иметь доступ к quester.routes.web, там есть функция, которая из данных генерирует request или просто url для ссылки итого получается циклическая зависимость quester.web -> quester.containers.history -> quester4.routes.web -> -> контроллер -> компонент -> хелпер для генерации запроса -> quester.routes.web что бы разорвать эту связь этот хэлпер нужно пробросить из history через контекст или props ниже в компоненты

kishanov19:01:20

ну вот ровно из-за такого же use case’а мы отказались от пробрасывания данных через props’ы 🙂

kishanov19:01:38

короче, не знаю насколько будет тебе полезно, но вот как я сейчас делаю раутинг

kishanov19:01:03

Берем https://github.com/funcool/bide, в нем описываем все рауты

kishanov19:01:27

в on-navigate пишем dispatch, которые складывает в “базу” route info

kishanov19:01:59

в любом месте, где нужна эта информация (href’ы там собрать или еще что) подписываемся на route-info и вычисляем

kishanov19:01:18

вьюха под писывается только уже на все готовенькое, никаких пропсов ниоткуда не приходит

kuzmin_m19:01:56

я не очень понял из описания т.е. ссылки href лежат в базе/атоме/глобальной переменной? и вьюха их готовыми получает?

kishanov19:01:29

(re-frame/reg-event-fx
  ::on-ui-navigate
  (fn [{:keys [db]} [_ name params query]]
    {:db (assoc db :route-info
                   {:name   name
                    :params params
                    :query  query})}))

kuzmin_m19:01:32

по поводу роутинга я пару месяцев назад показывал свою библиотеку роутинга из-за ошибок в дизайне и увлечением макросами полностью ее переписал https://github.com/darkleaf/router/blob/master/test/darkleaf/router_test.cljc ридми старое

kishanov19:01:35

вот упрощенный on-navigate

kuzmin_m19:01:11

это текущий роут?

kishanov19:01:12

Вот строим упрощенную navbar

kishanov19:01:15

(re-frame/reg-sub
  ::navbar-items
  :<- [::routes/route-info]
  :<- [::routes/ui-routes-labels]
  (fn [[route-info ui-routes-labels]]
    (->> [::routes/ui-testplan-list ::routes/ui-dashboard]
         (map (fn [k]
                {:label   (get ui-routes-labels k)
                 :active? (= k (:name route-info))
                 :href    (routes/ui-path-for k)})))))

kishanov19:01:30

это текущий раут

kishanov19:01:14

вот вьюха

(defn navbar []
  (let [navbar-items (re-frame/subscribe [::subs/navbar-items])]
    [re-com/h-box
     :children [[re-com/gap :size "82px"]
                [:a.navbar-brand {:href (:href (first @navbar-items))} "Lava Lamp"]
                [:ul.nav.navbar-nav
                 (doall
                   (for [{:keys [href label active?]} @navbar-items]
                     ^{:key href}
                     [:li {:class (when active? "active")}
                      [:a {:href href} label]]))]]]))

kishanov19:01:29

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

kishanov19:01:23

есть materialized view этого атома, который возвращает только те данные, которые нужны navbar’у (т.е. список объектов с label’ами и вычесленными href’ами)

kishanov19:01:33

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

kuzmin_m19:01:45

есть верстка - ui/quest-card

kuzmin_m19:01:57

а есть котейнер - quester.containers.adapters.quest-card

kuzmin_m19:01:16

и контейнер используя url-for вставляет урл в вьюху

kuzmin_m19:01:45

т.е. тут нет марериализованной вьюхи состояния

kishanov19:01:09

я и пытаюсь тебе сказать, что мы от вызова таких вот плюшек как url-for во вьюхах уходим, таким образом логики там не остается

kuzmin_m19:01:09

и я не очень понимаю как вы разорвали циклическую зависимость

kuzmin_m19:01:24

так это контейнер

kuzmin_m19:01:33

там должна быть “логика"

kuzmin_m19:01:54

или вы ее из контейнеров вынесли?

kuzmin_m19:01:11

как раз на data-flow

kishanov19:01:11

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

kuzmin_m19:01:45

а как вьюха так подписывается?

kishanov19:01:03

смотри выше (defn navbar

kishanov19:01:07

он через пропсы ничего не принимает, поэтому ему пофиг где рендериться: хоть child’ом чего нить, хоть в root маунти. У него один инвариант: чтобы на момент mount’а в базе лежали нужные данные

kishanov19:01:59

то, что я называю “контейнером” в нашем слуачае - это компоненты, которые делают логику типа “покажи спиннер, пока данные в нужное места складываются и не маунти остальные компоненты, пока там данных нет"

kuzmin_m19:01:38

я понял как оно сделано

kuzmin_m19:01:49

routes/ui-path-for - это глобальная штука?

kishanov19:01:08

(def ui-path-for (comp (partial str "#") (partial r/resolve ui-router)))

kishanov19:01:27

просто обертка вокруг bide чтобы не таскать с собой r/resolve везде

kuzmin_m20:01:04

а где подписывается на обновление страницы?

kuzmin_m20:01:42

(re-frame/reg-event-fx ::on-ui-navigate (fn [{:keys [db]} [_ name params query]] {:db (assoc db :route-info {:name name :params params :query query})}))

kishanov20:01:49

ну вот совсем упрощенный вариант

kishanov20:01:07

пошли мож в приват чтобы чат не засирать?

kishanov20:01:18

а то люди проснуться а тут такая простыня говнокода 🙂