Skip to content

Commit d5f90a5

Browse files
committed
Add section "Minimize the scope of DOM updates" to "Advanced Topics" page
1 parent 4aa19e6 commit d5f90a5

File tree

9 files changed

+152
-16
lines changed

9 files changed

+152
-16
lines changed

advanced.html

+16-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,21 @@ <h1 class="w3-padding-16 w3-xxxlarge">
8585
a: 1,
8686
b: 2,
8787
})
88-
</code></pre><p>More granular <code class="symbol">State</code> objects can help state bindings be more locally scoped, which make reactive UI updates more efficient by eliminating unnecessary DOM tree construction and replacement.</p><h3 class="w3-large w3-text-red" id="advanced-state-derivation"><a class="self-link" href="#advanced-state-derivation">Advanced state derivation</a></h3><blockquote><i>道生一,一生二,二生三,三生万物<br>(Tao derives one, one derives two, two derive three, and three derive everything)<br><br>-- 老子,道德经</i></blockquote><p>A broad set of advanced state derivation (derived states and side effects) can indeed be defined with <code class="symbol"><a href="/tutorial#api-derive" class="w3-hover-opacity">van.derive</a></code>, as illustrated in the following piece of code:</p><pre><code class="language-js">const fullName = van.state(localStorage.getItem("fullName") ?? "Tao Xin")
88+
</code></pre><p>More granular <code class="symbol">State</code> objects can help state bindings be more locally scoped, which make reactive UI updates more efficient by eliminating unnecessary DOM tree construction and replacement.</p><h3 class="w3-large w3-text-red" id="minimize-the-scope-of-dom-updates"><a class="self-link" href="#minimize-the-scope-of-dom-updates">Minimize the scope of DOM updates</a></h3><p>It's encouraged to organize your code in way that the scope of DOM updates is minimized upon the changes of <code class="symbol">State</code> objects. For instance, the 2 components below (<code class="symbol">Name1</code> and <code class="symbol">Name2</code>) are semantically equivalent:</p><pre><code class="language-js">const name = van.state("")
89+
90+
const Name1 = () =&gt; div(() =&gt; name.val.trim().length === 0 ?
91+
p("Please enter your name") :
92+
p("Hello ", b(name)),
93+
)
94+
95+
const Name2 = () =&gt; {
96+
const isNameEmpty = van.derive(() =&gt; name.val.trim().length === 0)
97+
return div(() =&gt; isNameEmpty.val ?
98+
p("Please enter your name") :
99+
p("Hello ", b(name)),
100+
)
101+
}
102+
</code></pre><p>But <code class="symbol">Name2</code>'s implementation is more preferable. With <code class="symbol">Name1</code>'s implementation, the entire <code class="language-html">&lt;p&gt;</code> element will be refreshed whenever <code class="symbol">name</code> state is updated. This is because the entire <code class="language-html">&lt;p&gt;</code> element is bound to <code class="symbol">name</code> state as specified in the binding function. On the other hand, with <code class="symbol">Name2</code>'s implementation, the <code class="language-html">&lt;p&gt;</code> element is only refreshed when <code class="symbol">name</code> state is changed from empty to non-empty, or vice versa, as the <code class="language-html">&lt;p&gt;</code> element is bound to derived state - <code class="symbol">isNameEmpty</code>. For other changes to <code class="symbol">name</code> state, only the <code class="symbol">Text node</code> inside the <code class="language-html">&lt;b&gt;</code> element will be refreshed.</p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/advanced/minimize-dom-updates">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="advanced-state-derivation"><a class="self-link" href="#advanced-state-derivation">Advanced state derivation</a></h3><blockquote><i>道生一,一生二,二生三,三生万物<br>(Tao derives one, one derives two, two derive three, and three derive everything)<br><br>-- 老子,道德经</i></blockquote><p>A broad set of advanced state derivation (derived states and side effects) can indeed be defined with <code class="symbol"><a href="/tutorial#api-derive" class="w3-hover-opacity">van.derive</a></code>, as illustrated in the following piece of code:</p><pre><code class="language-js">const fullName = van.state(localStorage.getItem("fullName") ?? "Tao Xin")
89103

90104
// State persistence with `localStorage`
91105
van.derive(() =&gt; localStorage.setItem("fullName", fullName.val))
@@ -206,7 +220,7 @@ <h1 class="w3-padding-16 w3-xxxlarge">
206220
return (renderPre.val ? pre : span)(text)
207221
})
208222
</code></pre><p>whenever <code class="symbol">renderPre</code> is toggled, a new <code class="symbol">text</code> state will be created and subscribe to changes of the <code class="symbol">prefix</code> state. However, the derivation from <code class="symbol">prefix</code> to the previous <code class="symbol">text</code> state will be garbage collected as the derivation was created while executing a binding function whose result DOM node no longer connects to the document tree. This is the mechanism to avoid memory leaks caused by state derivations that hold onto memory indefinitely.</p><p><a href="/code/gc-derive" class="w3-hover-opacity">Try out the example here</a> (You can use developer console to watch <code class="symbol">prefix</code>'s <code class="symbol">_listeners</code>).</p></div>
209-
<aside id="toc"><ul><li><a href="#dom-attributes-vs-properties" class="w3-hover-opacity">DOM Attributes vs. Properties</a></li><li><a href="#state-and-state-binding" class="w3-hover-opacity">State and State Binding</a><ul><li><a href="#why-not-dom-valued-states" class="w3-hover-opacity">Why can't states have DOM node as values?</a></li><li><a href="#state-granularity" class="w3-hover-opacity">State granularity</a></li><li><a href="#advanced-state-derivation" class="w3-hover-opacity">Advanced state derivation</a></li><li><a href="#conditional-state-binding" class="w3-hover-opacity">Conditional state binding</a></li><li><a href="#self-referencing-in-side-effects" class="w3-hover-opacity">Self-referencing in side effects</a></li></ul></li><li><a href="#gc" class="w3-hover-opacity">Garbage Collection</a><ul><li><a href="#avoid-your-bindings-to-be-gc-ed-unexpectedly" class="w3-hover-opacity">Avoid your bindings to be GC-ed unexpectedly</a></li><li><a href="#derived-states-and-side-effects-registered-inside-a-binding-function" class="w3-hover-opacity">Derived states and side effects registered inside a binding function</a></li></ul></li></ul></aside>
223+
<aside id="toc"><ul><li><a href="#dom-attributes-vs-properties" class="w3-hover-opacity">DOM Attributes vs. Properties</a></li><li><a href="#state-and-state-binding" class="w3-hover-opacity">State and State Binding</a><ul><li><a href="#why-not-dom-valued-states" class="w3-hover-opacity">Why can't states have DOM node as values?</a></li><li><a href="#state-granularity" class="w3-hover-opacity">State granularity</a></li><li><a href="#minimize-the-scope-of-dom-updates" class="w3-hover-opacity">Minimize the scope of DOM updates</a></li><li><a href="#advanced-state-derivation" class="w3-hover-opacity">Advanced state derivation</a></li><li><a href="#conditional-state-binding" class="w3-hover-opacity">Conditional state binding</a></li><li><a href="#self-referencing-in-side-effects" class="w3-hover-opacity">Self-referencing in side effects</a></li></ul></li><li><a href="#gc" class="w3-hover-opacity">Garbage Collection</a><ul><li><a href="#avoid-your-bindings-to-be-gc-ed-unexpectedly" class="w3-hover-opacity">Avoid your bindings to be GC-ed unexpectedly</a></li><li><a href="#derived-states-and-side-effects-registered-inside-a-binding-function" class="w3-hover-opacity">Derived states and side effects registered inside a binding function</a></li></ul></li></ul></aside>
210224
</div>
211225
</div>
212226
<script>

advanced/index.html

+16-2
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,21 @@ <h1 class="w3-padding-16 w3-xxxlarge">
8585
a: 1,
8686
b: 2,
8787
})
88-
</code></pre><p>More granular <code class="symbol">State</code> objects can help state bindings be more locally scoped, which make reactive UI updates more efficient by eliminating unnecessary DOM tree construction and replacement.</p><h3 class="w3-large w3-text-red" id="advanced-state-derivation"><a class="self-link" href="#advanced-state-derivation">Advanced state derivation</a></h3><blockquote><i>道生一,一生二,二生三,三生万物<br>(Tao derives one, one derives two, two derive three, and three derive everything)<br><br>-- 老子,道德经</i></blockquote><p>A broad set of advanced state derivation (derived states and side effects) can indeed be defined with <code class="symbol"><a href="/tutorial#api-derive" class="w3-hover-opacity">van.derive</a></code>, as illustrated in the following piece of code:</p><pre><code class="language-js">const fullName = van.state(localStorage.getItem("fullName") ?? "Tao Xin")
88+
</code></pre><p>More granular <code class="symbol">State</code> objects can help state bindings be more locally scoped, which make reactive UI updates more efficient by eliminating unnecessary DOM tree construction and replacement.</p><h3 class="w3-large w3-text-red" id="minimize-the-scope-of-dom-updates"><a class="self-link" href="#minimize-the-scope-of-dom-updates">Minimize the scope of DOM updates</a></h3><p>It's encouraged to organize your code in way that the scope of DOM updates is minimized upon the changes of <code class="symbol">State</code> objects. For instance, the 2 components below (<code class="symbol">Name1</code> and <code class="symbol">Name2</code>) are semantically equivalent:</p><pre><code class="language-js">const name = van.state("")
89+
90+
const Name1 = () =&gt; div(() =&gt; name.val.trim().length === 0 ?
91+
p("Please enter your name") :
92+
p("Hello ", b(name)),
93+
)
94+
95+
const Name2 = () =&gt; {
96+
const isNameEmpty = van.derive(() =&gt; name.val.trim().length === 0)
97+
return div(() =&gt; isNameEmpty.val ?
98+
p("Please enter your name") :
99+
p("Hello ", b(name)),
100+
)
101+
}
102+
</code></pre><p>But <code class="symbol">Name2</code>'s implementation is more preferable. With <code class="symbol">Name1</code>'s implementation, the entire <code class="language-html">&lt;p&gt;</code> element will be refreshed whenever <code class="symbol">name</code> state is updated. This is because the entire <code class="language-html">&lt;p&gt;</code> element is bound to <code class="symbol">name</code> state as specified in the binding function. On the other hand, with <code class="symbol">Name2</code>'s implementation, the <code class="language-html">&lt;p&gt;</code> element is only refreshed when <code class="symbol">name</code> state is changed from empty to non-empty, or vice versa, as the <code class="language-html">&lt;p&gt;</code> element is bound to derived state - <code class="symbol">isNameEmpty</code>. For other changes to <code class="symbol">name</code> state, only the <code class="symbol">Text node</code> inside the <code class="language-html">&lt;b&gt;</code> element will be refreshed.</p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/advanced/minimize-dom-updates">Try on jsfiddle</a></p><h3 class="w3-large w3-text-red" id="advanced-state-derivation"><a class="self-link" href="#advanced-state-derivation">Advanced state derivation</a></h3><blockquote><i>道生一,一生二,二生三,三生万物<br>(Tao derives one, one derives two, two derive three, and three derive everything)<br><br>-- 老子,道德经</i></blockquote><p>A broad set of advanced state derivation (derived states and side effects) can indeed be defined with <code class="symbol"><a href="/tutorial#api-derive" class="w3-hover-opacity">van.derive</a></code>, as illustrated in the following piece of code:</p><pre><code class="language-js">const fullName = van.state(localStorage.getItem("fullName") ?? "Tao Xin")
89103

90104
// State persistence with `localStorage`
91105
van.derive(() =&gt; localStorage.setItem("fullName", fullName.val))
@@ -206,7 +220,7 @@ <h1 class="w3-padding-16 w3-xxxlarge">
206220
return (renderPre.val ? pre : span)(text)
207221
})
208222
</code></pre><p>whenever <code class="symbol">renderPre</code> is toggled, a new <code class="symbol">text</code> state will be created and subscribe to changes of the <code class="symbol">prefix</code> state. However, the derivation from <code class="symbol">prefix</code> to the previous <code class="symbol">text</code> state will be garbage collected as the derivation was created while executing a binding function whose result DOM node no longer connects to the document tree. This is the mechanism to avoid memory leaks caused by state derivations that hold onto memory indefinitely.</p><p><a href="/code/gc-derive" class="w3-hover-opacity">Try out the example here</a> (You can use developer console to watch <code class="symbol">prefix</code>'s <code class="symbol">_listeners</code>).</p></div>
209-
<aside id="toc"><ul><li><a href="#dom-attributes-vs-properties" class="w3-hover-opacity">DOM Attributes vs. Properties</a></li><li><a href="#state-and-state-binding" class="w3-hover-opacity">State and State Binding</a><ul><li><a href="#why-not-dom-valued-states" class="w3-hover-opacity">Why can't states have DOM node as values?</a></li><li><a href="#state-granularity" class="w3-hover-opacity">State granularity</a></li><li><a href="#advanced-state-derivation" class="w3-hover-opacity">Advanced state derivation</a></li><li><a href="#conditional-state-binding" class="w3-hover-opacity">Conditional state binding</a></li><li><a href="#self-referencing-in-side-effects" class="w3-hover-opacity">Self-referencing in side effects</a></li></ul></li><li><a href="#gc" class="w3-hover-opacity">Garbage Collection</a><ul><li><a href="#avoid-your-bindings-to-be-gc-ed-unexpectedly" class="w3-hover-opacity">Avoid your bindings to be GC-ed unexpectedly</a></li><li><a href="#derived-states-and-side-effects-registered-inside-a-binding-function" class="w3-hover-opacity">Derived states and side effects registered inside a binding function</a></li></ul></li></ul></aside>
223+
<aside id="toc"><ul><li><a href="#dom-attributes-vs-properties" class="w3-hover-opacity">DOM Attributes vs. Properties</a></li><li><a href="#state-and-state-binding" class="w3-hover-opacity">State and State Binding</a><ul><li><a href="#why-not-dom-valued-states" class="w3-hover-opacity">Why can't states have DOM node as values?</a></li><li><a href="#state-granularity" class="w3-hover-opacity">State granularity</a></li><li><a href="#minimize-the-scope-of-dom-updates" class="w3-hover-opacity">Minimize the scope of DOM updates</a></li><li><a href="#advanced-state-derivation" class="w3-hover-opacity">Advanced state derivation</a></li><li><a href="#conditional-state-binding" class="w3-hover-opacity">Conditional state binding</a></li><li><a href="#self-referencing-in-side-effects" class="w3-hover-opacity">Self-referencing in side effects</a></li></ul></li><li><a href="#gc" class="w3-hover-opacity">Garbage Collection</a><ul><li><a href="#avoid-your-bindings-to-be-gc-ed-unexpectedly" class="w3-hover-opacity">Avoid your bindings to be GC-ed unexpectedly</a></li><li><a href="#derived-states-and-side-effects-registered-inside-a-binding-function" class="w3-hover-opacity">Derived states and side effects registered inside a binding function</a></li></ul></li></ul></aside>
210224
</div>
211225
</div>
212226
<script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*
2+
---
3+
authors:
4+
- Tao Xin
5+
resources:
6+
- https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.4.0.nomodule.min.js
7+
panel_js: 2
8+
...
9+
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const {b, div, i, input, p} = van.tags
2+
3+
const name = van.state("")
4+
5+
const Name1 = () => {
6+
const numRendered = van.state(0)
7+
return div(
8+
() => {
9+
++numRendered.val
10+
return name.val.trim().length === 0 ?
11+
p("Please enter your name") :
12+
p("Hello ", b(name))
13+
},
14+
p(i("The <p> element has been rendered ", numRendered, " time(s).")),
15+
)
16+
}
17+
18+
const Name2 = () => {
19+
const numRendered = van.state(0)
20+
const isNameEmpty = van.derive(() => name.val.trim().length === 0)
21+
return div(
22+
() => {
23+
++numRendered.val
24+
return isNameEmpty.val ?
25+
p("Please enter your name") :
26+
p("Hello ", b(name))
27+
},
28+
p(i("The <p> element has been rendered ", numRendered, " time(s).")),
29+
)
30+
}
31+
32+
van.add(document.body,
33+
p("Your name is: ", input({type: "text", value: name, oninput: e => name.val = e.target.value})),
34+
Name1(),
35+
Name2(),
36+
)

minimize-dom-updates-tracking.code.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
const {b, div, i, input, p} = van.tags
2+
3+
const name = van.state("")
4+
5+
const Name1 = () => {
6+
const numRendered = van.state(0)
7+
return div(
8+
() => {
9+
++numRendered.val
10+
return name.val.trim().length === 0 ?
11+
p("Please enter your name") :
12+
p("Hello ", b(name))
13+
},
14+
p(i("The <p> element has been rendered ", numRendered, " time(s).")),
15+
)
16+
}
17+
18+
const Name2 = () => {
19+
const numRendered = van.state(0)
20+
const isNameEmpty = van.derive(() => name.val.trim().length === 0)
21+
return div(
22+
() => {
23+
++numRendered.val
24+
return isNameEmpty.val ?
25+
p("Please enter your name") :
26+
p("Hello ", b(name))
27+
},
28+
p(i("The <p> element has been rendered ", numRendered, " time(s).")),
29+
)
30+
}
31+
32+
van.add(document.body,
33+
p("Your name is: ", input({type: "text", value: name, oninput: e => name.val = e.target.value})),
34+
Name1(),
35+
Name2(),
36+
)

minimize-dom-updates.code.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const name = van.state("")
2+
3+
const Name1 = () => div(() => name.val.trim().length === 0 ?
4+
p("Please enter your name") :
5+
p("Hello ", b(name)),
6+
)
7+
8+
const Name2 = () => {
9+
const isNameEmpty = van.derive(() => name.val.trim().length === 0)
10+
return div(() => isNameEmpty.val ?
11+
p("Please enter your name") :
12+
p("Hello ", b(name)),
13+
)
14+
}

sitegen/advanced.ts

+6
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ export default (doc: HTMLDocument) => {
4141
})
4242
`),
4343
p("More granular ", Symbol("State"), " objects can help state bindings be more locally scoped, which make reactive UI updates more efficient by eliminating unnecessary DOM tree construction and replacement."),
44+
H3("Minimize the scope of DOM updates"),
45+
p("It's encouraged to organize your code in way that the scope of DOM updates is minimized upon the changes of ", Symbol("State"), " objects. For instance, the 2 components below (", Symbol("Name1"), " and ", Symbol("Name2"), ") are semantically equivalent:"),
46+
JsFile("minimize-dom-updates.code.js"),
47+
p("But ", Symbol("Name2"), "'s implementation is more preferable. With ", Symbol("Name1"), "'s implementation, the entire ", InlineHtml("<p>"), " element will be refreshed whenever ", Symbol("name"), " state is updated. This is because the entire ", InlineHtml("<p>"), " element is bound to ", Symbol("name"), " state as specified in the binding function. On the other hand, with ", Symbol("Name2"), "'s implementation, the ", InlineHtml("<p>"), " element is only refreshed when ", Symbol("name"), " state is changed from empty to non-empty, or vice versa, as the ", InlineHtml("<p>"), " element is bound to derived state - ", Symbol("isNameEmpty"), ". For other changes to ", Symbol("name"), " state, only the ", Symbol("Text node"), " inside the ", InlineHtml("<b>"), " element will be refreshed."),
48+
JsFile("minimize-dom-updates-tracking.code.js", {}, {hidden: true}),
49+
p({id: "jsfiddle-minimize-dom-updates"}),
4450
H3("Advanced state derivation"),
4551
Quote({text: ["道生一,一生二,二生三,三生万物", br(), "(Tao derives one, one derives two, two derive three, and three derive everything)"], source: "老子,道德经"}),
4652
p("A broad set of advanced state derivation (derived states and side effects) can indeed be defined with ", SymLink("van.derive", "/tutorial#api-derive"), ", as illustrated in the following piece of code:"),

0 commit comments

Comments
 (0)