Skip to content

Commit 995f81d

Browse files
committed
Add section "Lifecycle Hooks" in "Advanced Topics" page
1 parent baddec3 commit 995f81d

File tree

10 files changed

+157
-22
lines changed

10 files changed

+157
-22
lines changed

advanced.html

+24-7
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,13 @@ <h1 class="w3-padding-16 w3-xxxlarge">
6666
option({value: "Vanilla"}),
6767
)
6868
)
69-
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/advanced/readonly-prop">Try on jsfiddle</a></p><p><b>NOTE:</b> for <b>Mini-Van</b>, since <code class="symbol">0.4.0</code>, we consistently assign the <code class="symbol">props</code> values via <code class="symbol">setAttribute</code> for all property keys in tag functions. This is because for SSR (server-side rendering), which is <b>Mini-Van</b>'s primary use case, setting the properties of a DOM node won't be visible in the rendered HTML string unless the action of setting the property itself will also set the corresponding HTML attribute (e.g.: setting the <code class="symbol">id</code> property of a DOM node will also set the <code class="symbol">id</code> attribute). This is helpful as <code class="language-js">input({type: "text", value: "value"})</code> can be rendered as <code class="language-html">&lt;input type="text" value="value"&gt;</code> in <b>Mini-Van</b> but would be rendered as <code class="language-html">&lt;input type="text"&gt;</code> if we set the property value via DOM property.</p><h2 class="w3-xxlarge w3-text-red" id="state-and-state-binding"><a class="self-link" href="#state-and-state-binding">State and State Binding</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><h3 class="w3-large w3-text-red" id="why-not-dom-valued-states"><a class="self-link" href="#why-not-dom-valued-states">Why can't states have DOM node as values?</a></h3><p>We might be prompted to assign a DOM node to a <code class="symbol">State</code> object, especially when the <code class="symbol">State</code> object is used as a <code class="symbol">State</code>-typed child node. However, this is problematic when the state is bound in multiple places, like the example below:</p><pre><code class="language-js">const {b, button, span} = van.tags
70-
71-
const TurnBold = () =&gt; {
69+
</code></pre><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/advanced/readonly-prop">Try on jsfiddle</a></p><p><b>NOTE:</b> for <b>Mini-Van</b>, since <code class="symbol">0.4.0</code>, we consistently assign the <code class="symbol">props</code> values via <code class="symbol">setAttribute</code> for all property keys in tag functions. This is because for SSR (server-side rendering), which is <b>Mini-Van</b>'s primary use case, setting the properties of a DOM node won't be visible in the rendered HTML string unless the action of setting the property itself will also set the corresponding HTML attribute (e.g.: setting the <code class="symbol">id</code> property of a DOM node will also set the <code class="symbol">id</code> attribute). This is helpful as <code class="language-js">input({type: "text", value: "value"})</code> can be rendered as <code class="language-html">&lt;input type="text" value="value"&gt;</code> in <b>Mini-Van</b> but would be rendered as <code class="language-html">&lt;input type="text"&gt;</code> if we set the property value via DOM property.</p><h2 class="w3-xxlarge w3-text-red" id="state-and-state-binding"><a class="self-link" href="#state-and-state-binding">State and State Binding</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><h3 class="w3-large w3-text-red" id="why-not-dom-valued-states"><a class="self-link" href="#why-not-dom-valued-states">Why can't states have DOM node as values?</a></h3><p>We might be prompted to assign a DOM node to a <code class="symbol">State</code> object, especially when the <code class="symbol">State</code> object is used as a <code class="symbol">State</code>-typed child node. However, this is problematic when the state is bound in multiple places, like the example below:</p><pre><code class="language-js">const TurnBold = () =&gt; {
7270
const vanJS = van.state("VanJS")
7371
return span(
7472
button({onclick: () =&gt; vanJS.val = b("VanJS")}, "Turn Bold"),
7573
" Welcome to ", vanJS, ". ", vanJS, " is awesome!"
7674
)
7775
}
78-
79-
van.add(document.body, TurnBold())
8076
</code></pre><p><b>Demo:</b> <span id="demo-dom-valued-state"></span></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/advanced/dom-valued-state">Try on jsfiddle</a></p><p>In this example, if we click the "Turn Bold" button, the first "<b>VanJS</b>" text will disappear, which is unexpected. This is because the same DOM node <code class="language-js">b("VanJS")</code> is used twice in the DOM tree. For this reason, an error will be thrown in <code class="symbol">van-{version}.debug.js</code> whenever we assign a DOM node to a <code class="symbol">State</code> object.</p><h3 class="w3-large w3-text-red" id="state-granularity"><a class="self-link" href="#state-granularity">State granularity</a></h3><p>Whenever possible, it's encouraged to define states at a more granular level. That is, it's recommended to define states like this:</p><pre><code class="language-js">const appState = {
8177
a: van.state(1),
8278
b: van.state(2),
@@ -219,8 +215,29 @@ <h1 class="w3-padding-16 w3-xxxlarge">
219215
const text = van.derive(() =&gt; `${prefix.val} - ${suffix.val}`)
220216
return (renderPre.val ? pre : span)(text)
221217
})
222-
</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>
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>
218+
</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><h2 class="w3-xxlarge w3-text-red" id="lifecycle-hooks"><a class="self-link" href="#lifecycle-hooks">Lifecycle Hooks</a></h2><hr style="width:50px;border:5px solid red" class="w3-round"><p>To keep <b>VanJS</b>'s simplicity, there isn't a direct support of lifecycle hooks in <b>VanJS</b> API. That being said, there are multiple ways to inject custom code upon lifecycle events (mount/unmount) of DOM elements.</p><h3 class="w3-large w3-text-red" id="using-settimeout"><a class="self-link" href="#using-settimeout">Using <code class="symbol">setTimeout</code></a></h3><p>A quick and dirty way to inject custom code upon a DOM element is mounted is to use <code class="symbol">setTimeout</code> with a small <code class="symbol">delay</code>. Since the rendering cycle starts right after the current thread of execution (internally, the rendering cycle is rescheduled via <code class="symbol">setTimeout</code> with a <code class="symbol">0</code> <code class="symbol">delay</code>), the custom code injected via <code class="symbol">setTimeout</code> with a small <code class="symbol">delay</code> is guaranteed to be executed right after the upcoming rendering cycle, which ensures its execution upon the DOM element being mounted. This simple technique is used in a few places of the official <b>VanJS</b> codebase (in the website and demos), e.g.: <a href="https://github.com/vanjs-org/vanjs-org.github.io/blob/e149ba503bf2da80d3023bb314e7fab57edbfa17/code/code-browser/src/main.js#L39" class="w3-hover-opacity">1</a>, <a href="https://github.com/vanjs-org/vanjs-org.github.io/blob/e149ba503bf2da80d3023bb314e7fab57edbfa17/converter-ui/convert.ts#L81" class="w3-hover-opacity">2</a>.</p><h3 class="w3-large w3-text-red" id="registering-a-side-effect-via-van-derive"><a class="self-link" href="#registering-a-side-effect-via-van-derive">Registering a side effect via <code class="symbol">van.derive</code></a></h3><p><i>Requires <b>VanJS</b> 1.5.0 or later.</i></p><p>If you want to get rid of <code class="symbol">setTimeout</code> (thus the small <code class="symbol">delay</code> introduced by it). You can leverage the technique of registering a side effect via <code class="symbol"><a href="/tutorial#api-derive" class="w3-hover-opacity">van.derive</a></code>, as demonstrated in the code below:</p><pre><code class="language-js">const Label = ({text, onmount}) =&gt; {
219+
if (onmount) {
220+
const trigger = van.state(false)
221+
van.derive(() =&gt; trigger.val &amp;&amp; onmount())
222+
trigger.val = true
223+
}
224+
return div({class: "label"}, text)
225+
}
226+
227+
const App = () =&gt; {
228+
const counter = van.state(0)
229+
return div(
230+
div(button({onclick: () =&gt; ++counter.val}, "Increment")),
231+
() =&gt; Label({
232+
text: counter.val,
233+
onmount: () =&gt; document.getElementById("msg").innerText =
234+
"Current label: " + document.querySelector(".label").innerText,
235+
}),
236+
div({id: "msg"}),
237+
)
238+
}
239+
</code></pre><p><b>Demo:</b></p><p id="demo-onmount"></p><p><a href="https://jsfiddle.net/gh/get/library/pure/vanjs-org/vanjs-org.github.io/tree/master/jsfiddle/advanced/onmount">Try on jsfiddle</a></p><p>This technique works because in <b>VanJS</b> 1.5.0 or later, derived states and side effects caused by state changes are scheduled asynchronously right in the next rendering cycle. Thus the side effects caused by the state changes of the current rendering cycle are guaranteed to be executed right after the completion of the current rendering cycle.</p><h3 class="w3-large w3-text-red" id="register-connectedcallback-and-disconnectedcallback-of-custom-elements"><a class="self-link" href="#register-connectedcallback-and-disconnectedcallback-of-custom-elements">Register <code class="symbol">connectedCallback</code> and <code class="symbol">disconnectedCallback</code> of custom elements</a></h3><p>Another option is to leverage the <code class="symbol">connectedCallback</code> and <code class="symbol">disconnectedCallback</code> of custom elements in <a href="https://developer.mozilla.org/en-US/docs/Web/API/Web_components" class="w3-hover-opacity">Web Components</a>. This is the only option to reliably inject custom code upon the unmount events of DOM elements. Note that in <b>VanJS</b>'s add-on: <a href="https://van-element.pages.dev/" class="w3-hover-opacity">van_element</a>, you can easily <a href="https://van-element.pages.dev/learn/lifecycle.html" class="w3-hover-opacity">register mount/unmount handlers</a> with the help of the add-on.</p></div>
240+
<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><li><a href="#lifecycle-hooks" class="w3-hover-opacity">Lifecycle Hooks</a><ul><li><a href="#using-settimeout" class="w3-hover-opacity">Using setTimeout</a></li><li><a href="#registering-a-side-effect-via-van-derive" class="w3-hover-opacity">Registering a side effect via van.derive</a></li><li><a href="#register-connectedcallback-and-disconnectedcallback-of-custom-elements" class="w3-hover-opacity">Register connectedCallback and disconnectedCallback of custom elements</a></li></ul></li></ul></aside>
224241
</div>
225242
</div>
226243
<script>

advanced.js

+28
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,31 @@
8080

8181
van.add(document.getElementById("demo-conditional-derive"), ConditionalDerive())
8282
}
83+
84+
{
85+
const {button, div} = van.tags
86+
87+
const Label = ({text, onmount}) => {
88+
if (onmount) {
89+
const trigger = van.state(false)
90+
van.derive(() => trigger.val && onmount())
91+
trigger.val = true
92+
}
93+
return div({class: "label"}, text)
94+
}
95+
96+
const App = () => {
97+
const counter = van.state(0)
98+
return div(
99+
div(button({onclick: () => ++counter.val}, "Increment")),
100+
() => Label({
101+
text: counter.val,
102+
onmount: () => document.getElementById("msg").innerText =
103+
"Current label: " + document.querySelector(".label").innerText,
104+
}),
105+
div({id: "msg"}),
106+
)
107+
}
108+
109+
van.add(document.getElementById("demo-onmount"), App())
110+
}

0 commit comments

Comments
 (0)