mithril-vndb/archive/v1.0.0-rc.8/keys.html
2017-01-26 20:46:32 -05:00

176 lines
7.9 KiB
HTML

<html>
<head>
<meta charset="UTF-8" />
<title>Mithril.js</title>
<link href='https://fonts.googleapis.com/css?family=Open+Sans' rel='stylesheet' type='text/css' />
<link href="lib/prism/prism.css" rel="stylesheet" />
<link href="style.css" rel="stylesheet" />
</head>
<body>
<header>
<section>
<h1>Mithril <small>1.0.0-rc.8</small></h1>
<nav>
<a href="index.html">Guide</a>
<a href="api.html">API</a>
<a href="https://gitter.im/lhorie/mithril.js">Chat</a>
<a href="https://github.com/lhorie/mithril.js">Github</a>
</nav>
</section>
</header>
<main>
<section>
<h1 id="keys">Keys</h1>
<ul>
<li>Tutorials<ul>
<li><a href="installation.html">Installation</a></li>
<li><a href="index.html">Introduction</a></li>
<li><a href="simple-application.html">Tutorial</a></li>
</ul>
</li>
<li>Resources<ul>
<li><a href="jsx.html">JSX</a></li>
<li><a href="es6.html">ES6</a></li>
<li><a href="css.html">CSS</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>
</ul>
</li>
<li>Key concepts<ul>
<li><a href="vnodes.html">Vnodes</a></li>
<li><a href="components.html">Components</a></li>
<li><a href="lifecycle-methods.html">Lifecycle methods</a></li>
<li><strong><a href="keys.html">Keys</a></strong><ul>
<li><a href="#what-are-keys">What are keys</a></li>
<li><a href="#how-to-use">How to use</a></li>
<li><a href="#debugging-key-related-issues">Debugging key related issues</a></li>
<li><a href="#avoid-anti-patterns">Avoid anti-patterns</a></li>
</ul>
</li>
<li><a href="autoredraw.html">Autoredraw system</a></li>
</ul>
</li>
<li>Social<ul>
<li><a href="https://github.com/lhorie/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>
</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>
</ul>
</li>
</ul>
<hr>
<h3 id="what-are-keys">What are keys</h3>
<p>Keys are a mechanism that allows re-ordering DOM elements within a NodeList, and mapping specific data items in a list to the respective DOM elements that are derived from them, as the data items move within the list.</p>
<p>In other words, a <code>key</code> is a way of saying &quot;this DOM element is for the data object with this id&quot;.</p>
<p>Typically, a <code>key</code> property should be the unique identifier field of the objects in the data array.</p>
<pre><code class="lang-javascript">var users = [
{id: 1, name: &quot;John&quot;},
{id: 2, name: &quot;Mary&quot;},
]
function userInputs(users) {
return users.map(function(u) {
return m(&quot;input&quot;, {key: u.id}, u.name)
})
}
m.render(document.body, userInputs(users))
</code></pre>
<p>Having a key means that if the <code>users</code> array is shuffled and the view is re-rendered, the inputs will be shuffled in the exact same order, so as to maintain correct focus and DOM state.</p>
<hr>
<h3 id="how-to-use">How to use</h3>
<p>A common pattern is to have data comprised of an array of objects and to generate a list of vnodes that map to each object in the array. For example, consider the following code:</p>
<pre><code class="lang-javascript">var people = [
{id: 1, name: &quot;John&quot;},
{id: 2, name: &quot;Mary&quot;},
]
function userList(users) {
return users.map(function(u) {
return m(&quot;button&quot;, u.name) // &lt;button&gt;John&lt;/button&gt;
// &lt;button&gt;Mary&lt;/button&gt;
})
}
m.render(document.body, userList(people))
</code></pre>
<p>Let&#39;s suppose the <code>people</code> variable was changed to this:</p>
<pre><code class="lang-javascript">people = [{id: 2, name: &quot;Mary&quot;}]
</code></pre>
<p>The problem is that from the point of view of the <code>userList</code> function, there&#39;s no way to tell if it was the first object that was removed, or if it was the second object that was removed <em>in addition to the first object&#39;s properties being modified</em>. If the first button was focused and the rendering engine removes it, then focus goes back to <code>&lt;body&gt;</code> as expected, but if the rendering engine removes the second button and modifies the text content of the first, then the focus will be on the wrong button after the update.</p>
<p>Worse still, if there were stateful jQuery plugins attached to these buttons, they could potentially have incorrect internal state after the update.</p>
<p>Even though in this particular example, we humans intuitively guess that the first item in the list was the one being removed, it&#39;s actually impossible for a computer to automatically solve this problem for all possible inputs.</p>
<p>Therefore, in the cases when a list of vnodes is derived from a dynamic array of data, you should add a <code>key</code> property to each virtual node that maps to a uniquely identifiable field in the source data. This will allow Mithril to intelligently re-order the DOM to maintain each DOM element correctly mapped to its respective item in the data source.</p>
<pre><code class="lang-javascript">function correctUserList(users) {
return users.map(function(u) {
return m(&quot;button&quot;, {key: u.id}, u.name)
})
}
</code></pre>
<hr>
<h3 id="debugging-key-related-issues">Debugging key related issues</h3>
<p>Keys can cause confusing issues if they are misunderstood. A typical symptom of key related issues is that application state appears to become corrupted after a few user interactions (usually involving a deletion).</p>
<h4 id="avoid-wrapper-elements-around-keyed-elements">Avoid wrapper elements around keyed elements</h4>
<p>Keys must be placed on the virtual node that is an immediate child of the array. This means that if you wrap the <code>button</code> in an <code>div</code> in the example above, the key must be moved to the <code>div</code>.</p>
<pre><code class="lang-javascript">// AVOID
users.map(function(u) {
return m(&quot;div&quot;, [ // key should be in `div`
m(&quot;button&quot;, {key: u.id}, u.name)
])
})
</code></pre>
<h4 id="avoid-hiding-keys-in-component-root-elements">Avoid hiding keys in component root elements</h4>
<p>If you refactor the code and put the button inside a component, the key must be moved out of the component and placed back where the component took the place of the button.</p>
<pre><code class="lang-javascript">// AVOID
var Button = {
view: function(vnode) {
return m(&quot;button&quot;, {key: vnode.attrs.id}, u.name)
}
}
users.map(function(u) {
return m(&quot;div&quot;, [
m(Button, {id: u.id}, u.name) // key should be here, not in component
])
})
</code></pre>
<h4 id="avoid-wrapping-keyed-elements-in-arrays">Avoid wrapping keyed elements in arrays</h4>
<p>Arrays are <a href="vnodes.html">vnodes</a>, and therefore keyable. You should not wrap arrays around keyed elements</p>
<pre><code class="lang-javascript">// AVOID
users.map(function(u) {
return [ // fragment is a vnode, and therefore keyable
m(&quot;button&quot;, {key: u.id}, u.name)
]
})
// PREFER
users.map(function(u) {
return m(&quot;button&quot;, {key: u.id}, u.name)
})
// PREFER
users.map(function(u) {
return m.fragment({key: u.id}, m(&quot;button&quot;, u.name))
})
</code></pre>
<h4 id="avoid-variable-types">Avoid variable types</h4>
<p>Keys must be strings if present or they will be cast to strings if they are not. Therefore, <code>&quot;1&quot;</code> (string) and <code>1</code> (number) are considered the same key.</p>
<p>You should use either strings or numbers as keys in one array, but not mix both.</p>
<pre><code class="lang-javascript">// AVOID
var things = [
{id: &quot;1&quot;, name: &quot;Book&quot;},
{id: 1, name: &quot;Cup&quot;},
]
</code></pre>
<hr />
<small>License: MIT. &copy; Leo Horie.</small>
</section>
</main>
<script src="lib/prism/prism.js"></script>
</body>
</html