Greetings to all! In this article, we will talk about a rather unusual topic, information about which for some reason I did not find, although it is quite useful in modern JavaScript frameworks and libraries for creating user interfaces, because, in some cases, applying the concept can help speed up work with the DOM several times.
The name is conditional, but the essence is important.
The problem of the usual state
The term "usual state" refers to data that is saved directly due to the state managers, or due to the internal functionality of the framework or library. An example of the state in Vue.js:
createApp({
setup() {
return {
count:ref(0);
};
},
template: `<div>
<button @click="count++">Click!</button>
<div>Clicks: {{ count }}</div>
</div>`,
}).mount("#app");
In this case, the state is stored directly in the object, which is returned in a predefined method of the framework.
So, DOM nodes can depend on a given state through different syntactic constructions. In the example, such a construction is the string {{ clicks }}
, which changes to the current data due to string interpolation.
Also, a commonly used syntactic construct is "loop". A loop is a keyword, either an attribute, or a method that explicitly defines that the creation of DOM nodes will occur, depending on the number of elements and on the values themselves, coming from the state. Example of a loop:
<template>
<tr
v-for="{ id, label } of rows"
:key="id"
:class="{ danger: id === selected }"
:data-label="label"
v-memo="[label, id === selected]"
>
<td class="col-md-1">{{ id }}</td>
<td class="col-md-4">
<a @click="select(id)">{{ label }}</a>
</td>
<td class="col-md-1">
<a @click="remove(id)">
<span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
</a>
</td>
<td class="col-md-6"></td>
</tr>
</template>
Let's say we want to update the class
of an element. Our data comes in the form of an array of objects. It is clear that the key must be explicitly specified in the object:You can get the value of the class in double curly brackets by the key, but that's the main problem, because it's slow.
To confirm my words, I will take the benchmarks of the cample.js framework (during the development of which I just noticed a similar problem). It is clearly visible there that a class that depends directly on the data of the normal state is set slower than a class that uses a temporary View state.
Let's take two versions of cample.js: 3.2.0-alpha.45 and 3.2.1-beta.0. There is such a line as “select row” (4 lines), which is exactly the main difference:
The data is taken from 126 and 128 benchmark releases.
As you can see from the image, the difference between one result and the other is almost one and a half times. I've been thinking for a long time about why this is so? I used to assume that the code was just slow, but the fact is different. If the data goes through a regular state, then it becomes necessary to go through all the data, even if we only change one letter in the value of the property in an n-ordinal object.
const oldData = [
{
id: 1,
label: "Text 1",
},
{ id: 2, label: "Text 2" },
{
id: 3,
label: "Text 3",
}
];
const newData = [
{
id: 1,
label: "Text 11", // 1 iteration, one letter has changed, but we're still looking further
},
{ id: 2, label: "Text 2" }, // 2 iteration
{
id: 3,
label: "Text 3", // 3 iteration
}
];
Therefore, it will always be slow, but this is the logically correct approach and this is the main joke of all modern frameworks and libraries for creating user interfaces. But what alternative can there be to this approach?
Temporary View state
Especially for such a problem, when it is necessary to introduce a separate state from the main one, so as not to go through the elements several times, you can use a certain concept in the code that will allow you to bind not to an object, but to an element. This concept is a temporary View state.
Its essence is as follows: We create a separate array for each element. It will be located in the code of the module itself, and we give the user methods that interact with this array in the callback function. Thus, the module will store approximately the following code:
{
el:li,
temporaryViewState:[{class:"value"}]
}
And in the project something like this:
setClass: [
(setData, event, eachTemporaryViewState) => () => {
const { setTemporaryViewState, clearTemporaryViewState } = eachTemporaryViewState;
clearTemporaryViewState();
setTemporaryViewState(() => {
return { class: "value" };
});
},
"updateClass",
],
Also, this array can be created only when the callback function is called, or simply create one array for all elements. This will allow us not to bind to the data that comes from the normal state, but to bind to a specific element that we want to update. That is, we create a temporary state that can be cleared and rewritten. This is well suited for those cases when we want to work with uncontrolled elements:
<!-- Controlled -->
<input type="text" value="{{ value }}" ::change="setValue()" />
<!-- Uncontrolled -->
<input type="text" class="{{ temporaryViewState.class }}" />
That is, it simply does not depend on the usual state directly, so in the DOM, this node can be said to be static (if we make a node template, then this element will skip).
Thus, we have a state that depends only on a specific element and on the callback function. When working with the "loop", you do not have to go through the entire data array to update one letter in one element. It will be enough just to call a specific function for a specific element and update a specific class.
This will allow you to achieve quick results in working with data and DOM. It is quite possible to apply this concept in modern frameworks and libraries and work with it.
Top comments (0)