mithril
Version:
A framework for building brilliant applications
230 lines (226 loc) • 13.4 kB
HTML
<head>
<meta charset=UTF-8>
<title> Virtual DOM nodes - Mithril.js</title>
<link href="https://fonts.googleapis.com/css?family=Open+Sans" rel=stylesheet>
<link href=style.css rel=stylesheet>
<link rel=icon type=image/png sizes=32x32 href=favicon.png>
<meta name=viewport content="width=device-width,initial-scale=1">
</head>
<body>
<header>
<section>
<a class=hamburger href=javascript:;>≡</a>
<h1><img src=logo.svg> Mithril <small>2.0.3</small></h1>
<nav>
<a href=index.html>Guide</a>
<a href=api.html>API</a>
<a href=https://gitter.im/MithrilJS/mithril.js>Chat</a>
<a href=https://github.com/MithrilJS/mithril.js>GitHub</a>
</nav>
</section>
</header>
<main>
<section>
<h1 id=virtual-dom-nodes><a href=#virtual-dom-nodes>Virtual DOM nodes</a></h1>
<ul>
<li>Getting Started<ul>
<li><a href=index.html>Introduction</a></li>
<li><a href=installation.html>Installation</a></li>
<li><a href=simple-application.html>Tutorial</a></li>
<li><a href=learning-mithril.html>Learning Resources</a></li>
<li><a href=support.html>Getting Help</a></li>
</ul>
</li>
<li>Resources<ul>
<li><a href=jsx.html>JSX</a></li>
<li><a href=es6.html>ES6+ on legacy browsers</a></li>
<li><a href=animation.html>Animation</a></li>
<li><a href=testing.html>Testing</a></li>
<li><a href=examples.html>Examples</a></li>
<li><a href=integrating-libs.html>3rd Party Integration</a></li>
<li><a href=paths.html>Path Handling</a></li>
</ul>
</li>
<li>Key concepts<ul>
<li><strong><a href=vnodes.html>Vnodes</a></strong><ul>
<li><a href=#what-is-virtual-dom>What is virtual DOM</a></li>
<li><a href=#basics>Basics</a></li>
<li><a href=#structure>Structure</a></li>
<li><a href=#vnode-types>Vnode types</a></li>
<li><a href=#monomorphic-class>Monomorphic class</a></li>
<li><a href=#avoid-anti-patterns>Avoid anti-patterns</a></li>
</ul>
</li>
<li><a href=components.html>Components</a></li>
<li><a href=lifecycle-methods.html>Lifecycle methods</a></li>
<li><a href=keys.html>Keys</a></li>
<li><a href=autoredraw.html>Autoredraw system</a></li>
</ul>
</li>
<li>Social<ul>
<li><a href=https://github.com/MithrilJS/mithril.js/wiki/JOBS>Mithril Jobs</a></li>
<li><a href=contributing.html>How to contribute</a></li>
<li><a href=credits.html>Credits</a></li>
<li><a href=code-of-conduct.html>Code of Conduct</a></li>
</ul>
</li>
<li>Misc<ul>
<li><a href=framework-comparison.html>Framework comparison</a></li>
<li><a href=change-log.html>Change log/Migration</a></li>
<li><a href=https://mithril.js.org/archive/v1.1.6/ >v1 Documentation</a></li>
<li><a href=https://mithril.js.org/archive/v0.2.5/ >v0.2 Documentation</a></li>
</ul>
</li>
</ul>
<hr>
<h3 id=what-is-virtual-dom><a href=#what-is-virtual-dom>What is virtual DOM</a></h3>
<p>A virtual DOM tree is a JavaScript data structure that describes a DOM tree. It consists of nested virtual DOM nodes, also known as <em>vnodes</em>.</p>
<p>The first time a virtual DOM tree is rendered, it is used as a blueprint to create a DOM tree that matches its structure.</p>
<p>Typically, virtual DOM trees are then recreated every render cycle, which normally occurs in response to event handlers or to data changes. Mithril <em>diffs</em> a vnode tree against its previous version and only modifies DOM elements in spots where there are changes.</p>
<p>It may seem wasteful to recreate vnodes so frequently, but as it turns out, modern JavaScript engines can create hundreds of thousands of objects in less than a millisecond. On the other hand, modifying the DOM is several orders of magnitude more expensive than creating vnodes.</p>
<p>For that reason, Mithril uses a sophisticated and highly optimized virtual DOM diffing algorithm to minimize the amount of DOM updates. Mithril <em>also</em> generates carefully crafted vnode data structures that are compiled by JavaScript engines for near-native data structure access performance. In addition, Mithril aggressively optimizes the function that creates vnodes as well.</p>
<p>The reason Mithril goes to such great lengths to support a rendering model that recreates the entire virtual DOM tree on every render is to provide a declarative <a href=https://en.wikipedia.org/wiki/Immediate_mode_(computer_graphics%29>immediate mode</a> API, a style of rendering that makes it drastically easier to manage UI complexity.</p>
<p>To illustrate why immediate mode is so important, consider the DOM API and HTML. The DOM API is an imperative <a href=https://en.wikipedia.org/wiki/Retained_mode>retained mode</a> API and requires 1. writing out exact instructions to assemble a DOM tree procedurally, and 2. writing out other instructions to update that tree. The imperative nature of the DOM API means you have many opportunities to micro-optimize your code, but it also means that you have more chances of introducing bugs and more chances to make code harder to understand.</p>
<p>In contrast, HTML is closer to an immediate mode rendering system. With HTML, you can write a DOM tree in a far more natural and readable way, without worrying about forgetting to append a child to a parent, running into stack overflows when rendering extremely deep trees, etc.</p>
<p>Virtual DOM goes one step further than HTML by allowing you to write <em>dynamic</em> DOM trees without having to manually write multiple sets of DOM API calls to efficiently synchronize the UI to arbitrary data changes.</p>
<hr>
<h3 id=basics><a href=#basics>Basics</a></h3>
<p>Virtual DOM nodes, or <em>vnodes</em>, are JavaScript objects that represent DOM elements (or parts of the DOM). Mithril's virtual DOM engine consumes a tree of vnodes to produce a DOM tree.</p>
<p>Vnodes are created via the <a href=hyperscript.html><code>m()</code></a> hyperscript utility:</p>
<pre><code class=language-javascript>m("div", {id: "test"}, "hello")</code></pre>
<p>Hyperscript can also consume <a href=components.html>components</a>:</p>
<pre><code class=language-javascript>// define a component
var ExampleComponent = {
view: function(vnode) {
return m("div", vnode.attrs, ["Hello ", vnode.children])
}
}
// consume it
m(ExampleComponent, {style: "color:red;"}, "world")
// equivalent HTML:
// <div style="color:red;">Hello world</div></code></pre>
<hr>
<h3 id=structure><a href=#structure>Structure</a></h3>
<p>Virtual DOM nodes, or <em>vnodes</em>, are JavaScript objects that represent an element (or parts of the DOM) and have the following properties:</p>
<table>
<thead>
<tr>
<th>Property</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>
<tr>
<td><code>tag</code></td>
<td><code>String|Object</code></td>
<td>The <code>nodeName</code> of a DOM element. It may also be the string <code>[</code> if a vnode is a fragment, <code>#</code> if it's a text vnode, or <code><</code> if it's a trusted HTML vnode. Additionally, it may be a component.</td>
</tr>
<tr>
<td><code>key</code></td>
<td><code>String?</code></td>
<td>The value used to map a DOM element to its respective item in a array of data.</td>
</tr>
<tr>
<td><code>attrs</code></td>
<td><code>Object?</code></td>
<td>A hashmap of <a href=hyperscript.html#dom-attributes>DOM attributes</a>, <a href=hyperscript.html#events>events</a>, <a href=hyperscript.html#properties>properties</a> and <a href=hyperscript.html#lifecycle-methods>lifecycle methods</a>.</td>
</tr>
<tr>
<td><code>children</code></td>
<td><code>(Array|String|Number|Boolean)?</code></td>
<td>In most vnode types, the <code>children</code> property is an array of vnodes. For text and trusted HTML vnodes, The <code>children</code> property is either a string, a number or a boolean.</td>
</tr>
<tr>
<td><code>text</code></td>
<td><code>(String|Number|Boolean)?</code></td>
<td>This is used instead of <code>children</code> if a vnode contains a text node as its only child. This is done for performance reasons. Component vnodes never use the <code>text</code> property even if they have a text node as their only child.</td>
</tr>
<tr>
<td><code>dom</code></td>
<td><code>Element?</code></td>
<td>Points to the element that corresponds to the vnode. This property is <code>undefined</code> in the <code>oninit</code> lifecycle method. In fragments and trusted HTML vnodes, <code>dom</code> points to the first element in the range.</td>
</tr>
<tr>
<td><code>domSize</code></td>
<td><code>Number?</code></td>
<td>This is only set in fragment and trusted HTML vnodes, and it's <code>undefined</code> in all other vnode types. It defines the number of DOM elements that the vnode represents (starting from the element referenced by the <code>dom</code> property).</td>
</tr>
<tr>
<td><code>state</code></td>
<td><code>Object?</code></td>
<td>An object that is persisted between redraws. It is provided by the core engine when needed. In POJO component vnodes, the <code>state</code> inherits prototypically from the component object/class. In class component vnodes it is an instance of the class. In closure components it is the object returned by the closure.</td>
</tr>
<tr>
<td><code>events</code></td>
<td><code>Object?</code></td>
<td>An object that is persisted between redraws and that stores event handlers so that they can be removed using the DOM API. The <code>events</code> property is <code>undefined</code> if there are no event handlers defined. This property is only used internally by Mithril, do not use or modify it.</td>
</tr>
<tr>
<td><code>instance</code></td>
<td><code>Object?</code></td>
<td>For components, a storage location for the value returned by the <code>view</code>. This property is only used internally by Mithril, do not use or modify it.</td>
</tr>
</table>
<hr>
<h3 id=vnode-types><a href=#vnode-types>Vnode types</a></h3>
<p>The <code>tag</code> property of a vnode determines its type. There are five vnode types:</p>
<table>
<thead>
<tr>
<th>Vnode type</th>
<th>Example</th>
<th>Description</th>
</tr>
</thead>
<tr>
<td>Element</td>
<td><code>{tag: "div"}</code></td>
<td>Represents a DOM element.</td>
</tr>
<tr>
<td>Fragment</td>
<td><code>{tag: "[", children: []}</code></td>
<td>Represents a list of DOM elements whose parent DOM element may also contain other elements that are not in the fragment. When using the <a href=hyperscript.html><code>m()</code></a> helper function, fragment vnodes can only be created by nesting arrays into the <code>children</code> parameter of <code>m()</code>. <code>m("[")</code> does not create a valid vnode.</td>
</tr>
<tr>
<td>Text</td>
<td><code>{tag: "#", children: ""}</code></td>
<td>Represents a DOM text node.</td>
</tr>
<tr>
<td>Trusted HTML</td>
<td><code>{tag: "<", children: "<br>"}</code></td>
<td>Represents a list of DOM elements from an HTML string.</td>
</tr>
<tr>
<td>Component</td>
<td><code>{tag: ExampleComponent}</code></td>
<td>If <code>tag</code> is a JavaScript object with a <code>view</code> method, the vnode represents the DOM generated by rendering the component.</td>
</tr>
</table>
<p>Everything in a virtual DOM tree is a vnode, including text. The <code>m()</code> utility automatically normalizes its <code>children</code> argument and turns strings into text vnodes and nested arrays into fragment vnodes.</p>
<p>Only element tag names and components can be the first argument of the <code>m()</code> function. In other words, <code>[</code>, <code>#</code> and <code><</code> are not valid <code>selector</code> arguments for <code>m()</code>. Trusted HTML vnodes can be created via <a href=trust.html><code>m.trust()</code></a></p>
<hr>
<h3 id=monomorphic-class><a href=#monomorphic-class>Monomorphic class</a></h3>
<p>The <code>mithril/render/vnode</code> module is used by Mithril to generate all vnodes. This ensures modern JavaScript engines can optimize virtual dom diffing by always compiling vnodes to the same hidden class.</p>
<p>When creating libraries that emit vnodes, you should use this module instead of writing naked JavaScript objects in order to ensure a high level of rendering performance.</p>
<hr>
<h3 id=avoid-anti-patterns><a href=#avoid-anti-patterns>Avoid anti-patterns</a></h3>
<h4 id=avoid-memoizing-mutable-vnodes><a href=#avoid-memoizing-mutable-vnodes>Avoid memoizing mutable vnodes</a></h4>
<p>Vnodes are supposed to represent the state of the DOM at a certain point in time. Mithril's rendering engine assumes a reused vnode is unchanged, so modifying a vnode that was used in a previous render will result in undefined behavior.</p>
<p>It is possible to reuse vnodes to prevent a diff, but it's preferable to use the <code>onbeforeupdate</code> hook to make your intent clear to other developers (or your future self).</p>
<hr>
<small>License: MIT. © Leo Horie.</small>
</section>
</main>
<script src=https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/prism.min.js defer></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-jsx.min.js defer></script>
<script src=https://unpkg.com/mithril@2.0.3/mithril.js async></script>
<script>
document.querySelector(".hamburger").onclick = function() {
document.body.className = document.body.className === "navigating" ? "" : "navigating"
document.querySelector("h1 + ul").onclick = function() {
document.body.className = ''
}
}
</script>