This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2017-01-08
Channels
- # aws (4)
- # beginners (81)
- # boot (65)
- # cljs-dev (10)
- # cljsjs (1)
- # cljsrn (12)
- # clojure (26)
- # clojure-austin (2)
- # clojure-dusseldorf (2)
- # clojure-russia (123)
- # clojure-spec (23)
- # clojure-uk (12)
- # clojurescript (36)
- # cursive (11)
- # datomic (39)
- # events (1)
- # hoplon (25)
- # incanter (4)
- # leiningen (3)
- # off-topic (5)
- # om (31)
- # re-frame (24)
- # reagent (13)
- # ring-swagger (2)
- # rum (10)
- # untangled (3)
- # yada (10)
Привет!
В react есть [“Higher-Order Components”](https://facebook.github.io/react/docs/higher-order-components.html).
Есть что-то оподобное для reagent?
Или нужно спускаться на уровень реакта, через reagent.core/create-class
и reagent.core/reactify-component
?
Я не нашел в гугле ничего подходящего.
Может быть у кого-то есть примеры из своих проектов?
В реагенте компонент - это же просто функция, так что чем не хватает композиции функций?
@dottedmag например, нужно через react context предать некую функцию, что бы она была у всех детей и детей детей. в том же redux сделано как: есть компонент Provider, который устанавливает контекст что бы не работать с контекстом напрямую используют HOС этот HOС работает с контекстом, а эту функцию предает через props обернутому компоненту
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)
> нужно через react context предать некую функцию, что бы она была у всех детей и детей детей.
есть комонент роутер есть комонент ссылка ссылка должна знать про роутер ссылка может быть на любой глубине внутри роутера
Ок, пример с роутером немного не подходящий, т.к. есть всего лишь один объект history. Но предположим, что есть следующее: комонент Root хочет передавать своим детям на любую глубину какое-то значение, возможно значение из своего состояния компонент C хочет принимать это значение и у нас в есть следующая иерархия
<div>
<Root param={1}>
....
…<C />....
</Root>
<Root param={1}>
....
…<C />....
</Root>
</div>
когда у нас одновременно два Root, вариант с глобальным атомом не работает и нужен именно контекст.дабы не светить работу с контекстом наружу, применяют HOC, который берет работу с контекстом на себя, а оборачиваемому компоненту передает значение через props
Сделай два ключа в хэшмапом, не мысли компонентами - функции и база данных, пусть дети сами лазают за тем, что им нужно
Что за ключ-то? Есть два варианта: либо это какой-то generic-компонент, типа из react-bootstrap, и тогда ему ключ должен подсунуть его родительский компонент, либо это какой-то частный компонент, и тогда он сам знает, что он рисует.
собственно работа с HOC вот тут
:reagent-render
(fn [& args]
(let [this (r/current-component)
foo (.. this -context -foo)]
(into [component foo] args)))}))
он берет компонент и вставляет певым аргументом нужное значение, за которое отвечает этот HOC
если интересно посмотреть, то это из проекта https://github.com/darkleaf/quester
@kuzmin_m все-таки не совсем понятно - для чего ты это делаешь ? протаскивать контекст - это же не самоцель. какая задача решается - можешь реальный пример привести?
В реакте это сделано, потому что там такой data flow через props вниз спускаются. Реагентовские приложения обычно этого не требуют. Собсно re-frame родился из-за того, что data flow надо было отделить от компонентов
короче, у меня есть приложения в котором компоненты вкладываются друг в друга как матрешка. Раньше мы делали такие higher order components (например, контейнер для rest запросов, который должен на component-did-mount
подсосать данные, распарсить http запрос, и если все хорошо прокинуть данные дальше для вьюхи, которая это рендерит)
сейчас мы от этого уходим, потому что в какой-то момент количество данных, которые спускаются через “context” начинает обрастать
и сходимся к тому, что компонент подписываесят на глобальный атом из которого все нужные данные сам соберет
https://github.com/darkleaf/quester/blob/master/src/ui/examples/main-page-example.jsx
(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]))})))
соответственно testplans-table
внутри себя подпишется на данные нужные данные, потому что после этого оно гарантировано будет в “глобальном атоме"
как только разделяешь глобальный state приложения и вьюхи, вся эта черная магия с тем, как прогнуться в react’овкий data flow уходит, потому что сам можешь достать данные когда надо и какие надо
https://www.youtube.com/watch?v=5hGHdETNteE - Nolen рассказывает про это где-то на 17й минуте
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.
https://github.com/darkleaf/quester/blob/master/src/ui/examples/main-page-example.jsx это заглушка
https://github.com/darkleaf/quester/blob/master/src/cljs/quester/containers/main_page.cljs
@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 ниже в компоненты
ну вот ровно из-за такого же use case’а мы отказались от пробрасывания данных через props’ы 🙂
Берем https://github.com/funcool/bide, в нем описываем все рауты
в любом месте, где нужна эта информация (href’ы там собрать или еще что) подписываемся на route-info и вычисляем
вьюха под писывается только уже на все готовенькое, никаких пропсов ниоткуда не приходит
я не очень понял из описания т.е. ссылки href лежат в базе/атоме/глобальной переменной? и вьюха их готовыми получает?
(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})}))
по поводу роутинга я пару месяцев назад показывал свою библиотеку роутинга из-за ошибок в дизайне и увлечением макросами полностью ее переписал https://github.com/darkleaf/router/blob/master/test/darkleaf/router_test.cljc ридми старое
(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)})))))
вот вьюха
(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]]))]]]))
есть materialized view этого атома, который возвращает только те данные, которые нужны navbar’у (т.е. список объектов с label’ами и вычесленными href’ами)
это не не правильная версия, но все же https://github.com/darkleaf/quester/blob/master/src/cljs/quester/containers/adapters/quest_card.cljs#L10
я и пытаюсь тебе сказать, что мы от вызова таких вот плюшек как url-for
во вьюхах уходим, таким образом логики там не остается
потому что вьюхи независимо подписываются на данные, данные беруться из базы. Даже если вьюхи друг в друга вложены, каждая из них независимо подписывается на данные и не знает ничего про то, откуда она рендерится
он через пропсы ничего не принимает, поэтому ему пофиг где рендериться: хоть child’ом чего нить, хоть в root маунти. У него один инвариант: чтобы на момент mount’а в базе лежали нужные данные
то, что я называю “контейнером” в нашем слуачае - это компоненты, которые делают логику типа “покажи спиннер, пока данные в нужное места складываются и не маунти остальные компоненты, пока там данных нет"