UP | HOME

A deep dive into Reagent that React interface for ClojureScript.
Reagent 자세히 알아보기.

Suddenly I wondered how Reagent works while using Reagent, I wanted to know "how to re-render a Reagent component/function".

I have known about RAtom of Reagent before digging Reagent. I assumed the 'deref' function of RAtom was hiding a secret. When I saw RAtom code in ratom.clj, I found 'notify-deref-watcher!' function in the 'deref' but I could not known what it is doing.

So I changed a strategy of digging Reagent:

  1. How to convert a function to a React element(component)?
  2. How to re-render a React component?

1 How to convert a function to a React element?

In summary, in process of converting a root function to a React component, Reagent wrap the root function with a function that convert a result of function to a React component. and create a React component, use the wrapped function as the 'render' function of it.

In Reagent v0.6.1, a flow to convert a function to a React element:

(core/render [f] ...)
 (dom/render [f] ...)
  (impl.template/as-element [f]) ; converting a root function to a React element.
   (impl.template/vec-to-elem [f])
    (impl.template/reag-element f [f]) ; React.createElement.
     (impl.component/as-class f)
      (impl.component/fn-to-class f)
       (impl.component/create-class {:reagent-render f, ...}) ; React.createClass
	(impl.component/cljsify {:reagent-render f, ...})
	 (impl.component/wrap-funs {:reagentRender f, ...}) ; {:render (:render static-fns), :reagentRender f, ...}
;; When rendering a React component:
(ratom/run-in-reaction #(do-render c), c ...) ; c = this
 (ratom/deref-capture #(do-render c) ...)
  (ratom/in-context #(do-render c) ...)
   (impl.component/do-render c)
    (impl.component/wrap-render c) ; retrieved the `f` from a React component by 'reagentRender' key.
     (impl.template/as-element (f))
      ;; If a result of the f is [:div [f2]]
      (impl.template/vec-to-elem [:div [f2]])
       (impl.template/native-element #js{:name "div", ...} [:div [f2]] ...)
	(impl.template/make-element [:div [f2]] "div" ...) ; recursively converting and return React element.
	 (impl.template/as-element [f2]) ; convert a child node of 'div'.
	  (impl.template/vec-to-elem [f2])
	   (impl.template/reag-element f2 [f2]) ; return React element.
	    (impl.component/as-class f2)
	     (impl.component/fn-to-class f2)
	      (impl.component/create-class {:reagent-render f2, ...}) ; return React component.
	       (impl.component/cljsify {:reagent-render f2, ...})
		(impl.component/wrap-funs {:reagentRender f2, ...}) ; {:render (:render static-fns), :reagentRender f2, ...}
;; When rendering a component:
;;...

2 How to re-render a React component?

In summary, if 'deref' function of a RAtom is called in the render function, Reagent register a watching function to the RAtom. and if the wathcing function is called, re-render a React component.

In Reagent v0.6.1, re-rendering flow:

;; When rendering a React component:
impl.component/static-fns
 (ratom/run-in-reaction #(do-render c), c, impl.batch/queue-render, ...) ; c = React component, assign a Reaction object.
  (ratom/deref-capture #(do-render c), r)
   (ratom/in-context r, #(do-render c)) ; binding `*ratom-context*` to the Reaction object.
    (impl.component/do-render c)
     (impl.component/wrap-render c) ; retrieved the `f` from a React component by 'reagentRender' key.
      (impl.template/as-element (f))
       ;; If user calls the 'deref' function of RAtom:
       (ratom.RAtom/-deref ratom)
	(ratom/notify-deref-watcher! ratom) ; use `*ratom-context*`.
	;; stored a RAtom obj to the 'captured' of Reaction obj.
   ;; If the 'captured' of Reaction obj changed:
   (ratom.Reaction/_update-watching r captured-ratoms)
    (ratom.RAtom/-add-watch each-captured-ratom r ratom/handle-reaction-change) ; = `add-watch`
     (ratom/add-w ratom r ratom/handle-reaction-change)
     ;; registered a watching function to the RAtom obj.
  ;; stored following fields to the Reaction obj:
  ;;  f        = #(do-render c)
  ;;  auto-run = #(impl.batch/queue-render c)
;; If the state of RAtom changed:
(ratom.RAtom/-reset! ratom ...)
 (ratom/notify-w ratom ...)
  (ratom/handle-reaction-change r ratom ...) ; ratom/handle-reaction-change is watching function.
   (ratom.Reaction/_handle-change r ratom ...)
    (#(impl.batching/queue-render c) ...) ; impl.batching/queue-render was stored in the 'auto-run' of Reaction obj.
     (impl.batching.RenderQueue/queue-render c ...)
      (impl.batching.RenderQueue/enqueue "componentQueue", c, ...) ; stored the React componenta to the 'componentQueue' of RenderQueue obj.
       (impl.batching.RenderQueue/schedule ...)
	(impl.batching/next-tick ...)
	;; registered impl.RenderQueue/run-queues fn to the call back of 'window.requestAnimationFrame'.
;; When calls the call back:
(impl.batching.RenderQueue/run-queues)
 (impl.batching.RenderQueue/flush-queues) ; retrieved the `c` from a React component by 'componentQueue' key.
  (impl.batching/run-queue)
   (.forceUpdate c)
   ;; React re-rednder the React component.