JavaScript 中临时视图状态的概念
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
大家好!在本文中,我们将讨论一个相当不寻常的话题,不知何故我没有找到相关信息,尽管它在现代 JavaScript 框架和用于创建用户界面的库中非常有用,因为在某些情况下,应用这个概念可以使 DOM 操作速度提高数倍。
名称是暂时的,但其本质很重要。
⚙️ 常态问题
“常规状态”指的是由于状态管理器或框架/库的内部功能而直接保存的数据。Vue.js 中的状态示例:
createApp({
setup() {
return {
count:ref(0);
};
},
template: `<div>
<button @click="count++">Click!</button>
<div>Clicks: {{ count }}</div>
</div>`,
}).mount("#app");
在这种情况下,状态直接存储在对象中,该对象通过框架的预定义方法返回。
因此,DOM 节点可以通过不同的语法结构依赖于给定的状态。例如,字符串 `<value>` 就是这样一种结构{{ clicks }},它会因为字符串插值而变为当前数据。
此外,常用的语法结构是“循环”。循环是一个关键字、一个属性或一个方法,它明确定义了根据元素数量和状态值来创建 DOM 节点。循环示例:
<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>
假设我们要更新class一个元素的属性。我们的数据以对象数组的形式存在。显然,键必须在对象中显式指定:虽然可以通过键在双花括号中获取类的值,但这恰恰是主要问题所在,因为这种方法速度很慢。
为了验证我的说法,我将使用 cample.js 框架的基准测试结果(在开发过程中,我刚刚注意到一个类似的问题)。测试结果清楚地表明,直接依赖于常规状态数据的类比使用临时视图状态的类加载速度更慢。
我们来看两个版本的 cample.js:3.2.0-alpha.45 和 3.2.1-beta.0。它们之间有一行名为“select row”(共 4 行)的代码,这正是它们的主要区别:
如图所示,两个结果之间的差异几乎是 1.5 倍。我一直在思考这是为什么?我以前以为是代码运行缓慢,但事实并非如此。如果数据处于常规状态,那么即使我们只更改一个 n 阶对象中某个属性值的一个字母,也需要遍历所有数据。
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
}
];
因此,这种方法总是很慢,但这在逻辑上是正确的,也是所有现代用户界面框架和库最大的笑点所在。但是,除了这种方法之外,还有什么替代方案呢?
🔧 临时视图状态
尤其对于这类问题,当需要引入一个独立于主状态之外的独立状态,以避免多次遍历元素时,可以使用代码中的一种概念,它允许你将状态绑定到元素而非对象。这种概念就是临时视图状态。
其本质如下:我们为每个元素创建一个单独的数组。该数组位于模块自身的代码中,我们在回调函数中提供与该数组交互的方法。因此,该模块将存储大致如下的代码:
{
el:li,
temporaryViewState:[{class:"value"}]
}
项目中大概是这样的:
setClass: [
(setData, event, eachTemporaryViewState) => () => {
const { setTemporaryViewState, clearTemporaryViewState } = eachTemporaryViewState;
clearTemporaryViewState();
setTemporaryViewState(() => {
return { class: "value" };
});
},
"updateClass",
],
此外,这个数组可以仅在回调函数被调用时创建,或者也可以直接创建一个包含所有元素的数组。这样,我们就可以不绑定到来自正常状态的数据,而是绑定到我们想要更新的特定元素。也就是说,我们创建了一个可以清除和重写的临时状态。这非常适合处理非受控元素的情况:
<!-- Controlled -->
<input type="text" value="{{ value }}" ::change="setValue()" />
<!-- Uncontrolled -->
<input type="text" class="{{ temporaryViewState.class }}" />
也就是说,它并不直接依赖于通常的状态,因此在 DOM 中,这个节点可以说是静态的(如果我们创建一个节点模板,那么这个元素将被跳过)。
因此,我们得到的状态仅依赖于特定元素和回调函数。使用“循环”时,无需遍历整个数据数组来更新某个元素中的一个字母。只需针对特定元素调用特定函数并更新特定类即可。
这将使您能够快速有效地处理数据和 DOM。完全可以将这一概念应用于现代框架和库中并加以利用。
💬 后记
这篇文章是我在2024年9月写的。当时我正在做另一个项目,但现在我觉得这个话题或许对其他人也有用。我发现大多数现代解决方案的点击速度都存在很大的问题。也许这篇文章能帮到一些人(是的,我写这篇文章不仅仅是为了介绍一个新项目!)。
文章来源:https://dev.to/anthonymax/the-concept-of-a-temporary-view-state-in-javascript-2ag3
