-
-
Save zelark/135374eb3c3b3ac3a5c8b5ca2a21e22c to your computer and use it in GitHub Desktop.
Deconstructing React
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns react-fun.part1) | |
;;Deconstructing React | |
;; v = f (d) | |
;;let an element be a vector [type props & children] | |
;; type - (or keyword? fn?) | |
;; props - map? | |
(defn MyComponent [style & children] | |
(into [:div {:style style}] children)) | |
(defn MyChildComponent [text] | |
[:span text]) | |
(defn MyAnotherChildComponent [attrs] | |
[:input {:type :button | |
:class (if (:enabled? attrs) "enabled" "disabled")}]) | |
(defn RootComponent [] | |
[MyComponent {:color :blue} | |
[MyChildComponent "10"] | |
[MyAnotherChildComponent {:enabled? true}]]) | |
[RootComponent] | |
[:div {:style {:color :blue}} | |
[:span "10"] | |
[:input {:type :button | |
:class "enabled"}]] | |
(def elt? vector?) | |
(defn user-elt? [elt] | |
(and (elt? elt) (fn? (first elt)))) | |
(defn primitive-elt? [elt] | |
(and (elt? elt) (keyword? (first elt)))) | |
(defn primitive-component [& args] | |
(into [] (filter some? args))) | |
(defn build-component [elt] | |
(cond | |
(user-elt? elt) (build-component (apply (first elt) (rest elt))) | |
(primitive-elt? elt) (apply primitive-component (first elt) | |
(map build-component (rest elt))) | |
:else elt)) | |
(build-component [RootComponent]) | |
[:div {:style {:color :blue}} | |
[:span "10"] | |
[:input {:type :button, :class "enabled"}]] | |
(def app? vector?) | |
(defn eval' [x] | |
(cond | |
(and (app? x) (macro? (first x))) (eval' (apply (first x) (rest x))) | |
(app? x) (apply (first x) (map eval' (rest x))) | |
:else x)) | |
(defn build-component [elt] | |
(cond | |
(user-elt? elt) (build-component (apply (first elt) (rest elt))) | |
(primitive-elt? elt) (apply primitive-component (first elt) | |
(map build-component (rest elt))) | |
:else elt)) | |
(defn user-component [elt subst] | |
{:elt elt | |
:subst subst}) | |
(defn build-component' [elt] | |
(cond | |
(user-elt? elt) (user-component elt (build-component' (apply (first elt) (rest elt)))) | |
(primitive-elt? elt) (apply primitive-component (first elt) | |
(map build-component' (rest elt))) | |
:else elt)) | |
(build-component' [RootComponent]) | |
{:elt [#function[react-fun.part1/RootComponent]], | |
:subst | |
{:elt | |
[#function[react-fun.part1/MyComponent] | |
{:color :blue} | |
[#function[react-fun.part1/MyChildComponent] "10"] | |
[#function[react-fun.part1/MyAnotherChildComponent] {:enabled? true}]], | |
:subst | |
[:div | |
{:style {:color :blue}} | |
{:elt [#function[react-fun.part1/MyChildComponent] "10"], :subst [:span "10"]} | |
{:elt [#function[react-fun.part1/MyAnotherChildComponent] {:enabled? true}], :subst [:input {:type :button, :class "enabled"}]}]}} | |
(def comp (build-component' [RootComponent])) | |
(let [[comp' updates] | |
(reconcile comp [RootComponent 2])]) | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns react-fun.part2) | |
(defn longest [xs ys] (if (> (count xs) (count ys)) xs ys)) | |
(defn lcs [a b] | |
(let [impl* (atom nil) | |
impl (memoize | |
(fn [[x & xs] [y & ys]] | |
(cond | |
(or (= x nil) (= y nil) ) nil | |
(= x y) (cons x (@impl* xs ys)) | |
:else (longest (@impl* (cons x xs) ys) (@impl* xs (cons y ys))))))] | |
(reset! impl* impl) | |
(impl a b))) | |
(defprotocol Toolkit | |
(make-node [tk e]) | |
(perform-updates [tk u])) | |
(defn get-children [[_ props & children]] | |
(cond | |
(map? props) children | |
(some? props) (cons props children) | |
:else children)) | |
(defn get-props [[_ props & children]] | |
(if (map? props) props nil)) | |
(defn get-key [[type :as e] indices] | |
(if-let [key (or (:key (get-props e)) (:key (meta e)))] | |
[key indices] | |
(let [indices' (update indices type (fn [i] (if i (inc i) 0))) | |
key (indices' type)] | |
[[type key] indices']))) | |
(defn user-component? [[type]] | |
(fn? type)) | |
(defn elts-with-keys [children] | |
(second (reduce (fn [[indices res] e] | |
(let [[key indices'] (get-key e indices)] | |
[indices' (conj res {:elt e | |
:key key})])) | |
[{} []] children))) | |
(defn build-component [{[type & args :as elt] :elt key :key} tk] | |
(if (user-component? elt) | |
(let [subst (apply type args) | |
[c-subst updates] (build-component {:elt subst | |
:key key} tk)] | |
[{:component/node (:component/node c-subst) | |
:component/key key | |
:component/element elt | |
:component/subst c-subst} | |
updates]) | |
(let [new-node (make-node tk elt) | |
built (map #(build-component % tk) (elts-with-keys (get-children elt))) | |
c-components (map first built) | |
updates (mapcat second built)] | |
[{:component/node new-node | |
:component/key key | |
:component/element elt | |
:component/children c-components} | |
(concat updates | |
(map-indexed (fn [i c] | |
{:update/type :add | |
:add/index i | |
:add/parent new-node | |
:add/child (:component/node c)}) | |
c-components))]))) | |
(declare reconcile) | |
(defn reconcile-children [{c-children :component/children | |
c-element :component/element | |
c-key :component/key | |
c-node :component/node :as c} e tk] | |
(let [new-children (elts-with-keys (get-children e)) | |
old-keys (map :component/key c-children) | |
new-keys (map :key new-children)] | |
(if (not= old-keys new-keys) | |
(let [common (into #{} (lcs old-keys new-keys)) | |
key->component (->> c-children | |
(map (fn [{key :component/key :as c}] [key c])) | |
(into {})) | |
recon (map (fn [{e :elt | |
k :key :as child}] | |
(if-let [old-c (key->component k)] | |
(reconcile old-c child tk) | |
(build-component child tk))) | |
new-children) | |
new-keys-set (into #{} new-keys) | |
removes (->> c-children | |
(remove #(contains? common (:component/key %))) | |
(mapcat | |
(fn [{node :component/node | |
key :component/key}] | |
(cond-> [{:update/type :remove | |
:remove/child node}] | |
(not (contains? new-keys-set key)) | |
(conj {:update/type :destroy | |
:destroy/node node}))))) | |
new-c-children (map first recon) | |
adds (->> new-c-children | |
(keep-indexed (fn [i {k :component/key | |
e :component/element | |
child-node :component/node}] | |
(when-not (contains? common k) | |
{:update/type :add | |
:add/index i | |
:add/child child-node | |
:add/parent c-node}))))] | |
[(map first recon) (concat removes (mapcat second recon) adds)]) | |
(let [recon (map (fn [i c elt] | |
(reconcile c elt i tk)) | |
(range) c-children (get-children e))] | |
[(map first recon) (mapcat second recon)])))) | |
(def get-type first) | |
(defn reconcile-primitive [{old-e :component/element | |
node :component/node :as c} {elt :elt :as e} tk] | |
(if (not= (get-type old-e) (get-type elt)) | |
(let [[new-c updates] (build-component e tk)] | |
[new-c (concat updates [{:update/type :remove | |
:remove/child node} | |
{:update/type :destroy | |
:destroy/node node}])]) | |
(let [[children-reconciled children-updates] (reconcile-children c elt tk) | |
[old-props new-props] [(get-props old-e) (get-props elt)] | |
props-updates (when (not= old-props new-props) | |
[{:update/type :update-props | |
:update-props/node node | |
:update-props/node-type (first e) | |
:update-props/old-props old-props | |
:update-props/new-props new-props}])] | |
[(assoc c | |
:component/element elt | |
:component/children children-reconciled) | |
(concat props-updates children-updates)]))) | |
(defn reconcile-user [{c-subst :component/subst | |
old-elt :component/element :as c} {[type & args :as elt] :elt key :key} tk] | |
(let [subst (if (not= elt old-elt) | |
(apply type args) | |
(:component/element c-subst)) | |
[new-c-subst updates] (reconcile c-subst {:elt subst | |
:key key} tk)] | |
[(assoc c | |
:component/subst new-c-subst | |
:component/element elt | |
:component/node (:component/node new-c-subst)) | |
updates])) | |
(defn reconcile [c e tk] | |
(if (user-component? (:elt e)) | |
(reconcile-user c e tk) | |
(reconcile-primitive c e tk))) | |
(defn label [x] | |
[:label {:text (str x)}]) | |
(defn labels [x] | |
(into [:div] | |
(map (fn [i] [label i]) (range x)))) | |
(def x (atom 0)) | |
(def tt (reify Toolkit | |
(make-node [tk e] | |
(swap! x inc)) | |
(perform-updates [tk u]))) | |
(def comp (first (build-component {:elt [labels 1] | |
:key 0} tt))) | |
(reconcile comp {:elt [labels 3] :key 0} tt) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment