176 lines
7.9 KiB
HTML
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 "this DOM element is for the data object with this id".</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: "John"},
|
|
{id: 2, name: "Mary"},
|
|
]
|
|
|
|
function userInputs(users) {
|
|
return users.map(function(u) {
|
|
return m("input", {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: "John"},
|
|
{id: 2, name: "Mary"},
|
|
]
|
|
|
|
function userList(users) {
|
|
return users.map(function(u) {
|
|
return m("button", u.name) // <button>John</button>
|
|
// <button>Mary</button>
|
|
})
|
|
}
|
|
|
|
m.render(document.body, userList(people))
|
|
</code></pre>
|
|
<p>Let's suppose the <code>people</code> variable was changed to this:</p>
|
|
<pre><code class="lang-javascript">people = [{id: 2, name: "Mary"}]
|
|
</code></pre>
|
|
<p>The problem is that from the point of view of the <code>userList</code> function, there'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's properties being modified</em>. If the first button was focused and the rendering engine removes it, then focus goes back to <code><body></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'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("button", {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("div", [ // key should be in `div`
|
|
m("button", {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("button", {key: vnode.attrs.id}, u.name)
|
|
}
|
|
}
|
|
users.map(function(u) {
|
|
return m("div", [
|
|
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("button", {key: u.id}, u.name)
|
|
]
|
|
})
|
|
|
|
// PREFER
|
|
users.map(function(u) {
|
|
return m("button", {key: u.id}, u.name)
|
|
})
|
|
|
|
// PREFER
|
|
users.map(function(u) {
|
|
return m.fragment({key: u.id}, m("button", 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>"1"</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: "1", name: "Book"},
|
|
{id: 1, name: "Cup"},
|
|
]
|
|
</code></pre>
|
|
|
|
<hr />
|
|
<small>License: MIT. © Leo Horie.</small>
|
|
</section>
|
|
</main>
|
|
<script src="lib/prism/prism.js"></script>
|
|
</body>
|
|
</html
|