I'm writing an app in Svelte, and I have a page on which there is a list of values displayed as a table. I want to be able to edit these values, and one of the values is chosen by a select
element with about 100 possible values.
我正在用Svelte写一个应用程序,我有一个页面,上面有一个以表格形式显示的值列表。我希望能够编辑这些值,其中一个值是由一个选择元素选择的,该元素有大约100个可能的值。
There is a button that toggles the display between table display and form edit mode (with the select
elements).
有一个按钮可以在表格显示和表单编辑模式(带有选择元素)之间切换显示。
The problem is that if there are several lines that create select
items each with the same 100 possible option values, it takes Svelte a very long time to generate the form (about a full second to switch to edit mode when there are about 15 select
elements). I didn't have this problem with React on the same app (I'm porting most of the app from React to Svelte) so I suppose there's something I'm doing wrong or that there is a suble way of making things faster...
问题是,如果有几行创建了选择项,每行都有相同的100个可能的选项值,则Svelte需要很长时间才能生成表单(当大约有15个选择元素时,大约一整秒切换到编辑模式)。我在同一个应用程序上的Reaction没有这个问题(我正在将大部分应用程序从Reaction移植到Svelte),所以我想我做错了什么,或者有一个合适的方法可以让事情变得更快……
Here's a small working example to illustrate :
下面是一个小的工作示例来说明:
<script>
const pokemonList = ["bulbasaur", "ivysaur", "venusaur", "charmander", "charmeleon", "charizard", "squirtle", "wartortle", "blastoise", "caterpie", "metapod", "butterfree", "weedle", "kakuna", "beedrill", "pidgey", "pidgeotto", "pidgeot", "rattata", "raticate", "spearow", "fearow", "ekans", "arbok", "pikachu", "raichu", "sandshrew", "sandslash", "nidoran-f", "nidorina", "nidoqueen", "nidoran-m", "nidorino", "nidoking", "clefairy", "clefable", "vulpix", "ninetales", "jigglypuff", "wigglytuff", "zubat", "golbat", "oddish", "gloom", "vileplume", "paras", "parasect", "venonat", "venomoth", "diglett", "dugtrio", "meowth", "persian", "psyduck", "golduck", "mankey", "primeape", "growlithe", "arcanine", "poliwag", "poliwhirl", "poliwrath", "abra", "kadabra", "alakazam", "machop", "machoke", "machamp", "bellsprout", "weepinbell", "victreebel", "tentacool", "tentacruel", "geodude", "graveler", "golem", "ponyta", "rapidash", "slowpoke", "slowbro", "magnemite", "magneton", "farfetchd", "doduo", "dodrio", "seel", "dewgong", "grimer", "muk", "shellder", "cloyster", "gastly", "haunter", "gengar", "onix", "drowzee", "hypno", "krabby", "kingler", "voltorb", "electrode", "exeggcute", "exeggutor", "cubone", "marowak", "hitmonlee", "hitmonchan", "lickitung", "koffing", "weezing", "rhyhorn", "rhydon", "chansey", "tangela", "kangaskhan", "horsea", "seadra", "goldeen", "seaking", "staryu", "starmie", "mr-mime", "scyther", "jynx", "electabuzz", "magmar", "pinsir", "tauros", "magikarp", "gyarados", "lapras", "ditto", "eevee", "vaporeon", "jolteon", "flareon", "porygon", "omanyte", "omastar", "kabuto", "kabutops", "aerodactyl", "snorlax", "articuno", "zapdos", "moltres", "dratini", "dragonair", "dragonite", "mewtwo", "mew"];
let team = [
"bulbasaur",
"charmander",
"squirtle",
"caterpie",
"weedle",
"pidgey",
"rattata",
"spearow",
"ekans",
"pikachu",
"sandshrew",
"nidoran-f",
"nidoran-m",
"clefairy",
"vulpix",
"jigglypuff",
];
let isEditing = false;
</script>
<main>
{#if isEditing}
<form>
{#each team as teamMember}
<div>
<select bind:value={teamMember}>
{#each pokemonList as pokemonName}
<option value={pokemonName}>{pokemonName}</option>
{/each}
</select>
</div>
{/each}
</form>
{:else}
<table>
{#each team as teamMember}
<tr>
<td>{teamMember}</td>
</tr>
{/each}
</table>
{/if}
<button on:click={() => (isEditing = !isEditing)}>Toggle</button>
</main>
Note that all the select that are generated have exactly the same list of options, so is there a way to generate the element once and reuse it each time? Or any other solution that would make this much more responsive? In the example, if I only keep one line then toggling to editing mode is almost instantaneous, but it takes significantly longer as more lines are added.
请注意,生成的所有SELECT都有完全相同的选项列表,所以有没有一种方法可以一次生成元素并在每次都重用它?或任何其他解决方案,将使这一点更具响应性?在这个例子中,如果我只保留一行,那么切换到编辑模式几乎是瞬间的,但随着添加更多的行,所需的时间要长得多。
更多回答
I wonder if the performance hit is also seen in Vanilla JS. I think it would be worth creating a Vanilla JS project that does the same and see if this is a Svelte problem or not. If Vanilla JS exhibits the same problem, I guess you're stuck with this.
我想知道在Vanilla JS中是否也看到了性能的冲击。我认为有必要创建一个Vanilla JS项目来做同样的事情,看看这是不是一个Svelte问题。如果Vanilla JS表现出同样的问题,我猜您就只能这样了。
Note that there are various builds and that a development Svelte build will perform worse that a production build. Also SSR vs CSR can have different performance characteristics and different browsers handle large HTML fragments differently.
请注意,有各种构建,开发Svelte构建的性能比生产构建的性能更差。此外,SSR和CSR可能具有不同的性能特征,并且不同的浏览器处理大的HTML片段的方式也不同。
Your machine might be a bit underpowered or your browser might perform particularly badly; your code in the REPL takes ~25ms on mine in Chrome or Firefox.
你的机器可能动力不足,或者你的浏览器可能运行得特别差;你在REPL中的代码在我的Chrome或Firefox上花费了大约25ms。
Still, this scenario can be optimized in at least two ways:
尽管如此,这种情况至少可以通过两种方式进行优化:
- Only show the select when interacting with that one specific item (e.g. on clicking on it) - should be straightforward, not great UX
- Only generate the items in the lists, when they become relevant, i.e. focus is on the element
For the latter approach, you can separate the generation of the options into a separate component, e.g.
对于后一种方法,您可以将选项的生成分离到单独的组件中,例如
<select bind:value={teamMember}>
<LazyOptions list={pokemonList} value={teamMember} />
</select>
<!-- LazyOptions.svelte -->
<script>
export let list;
export let value;
let focused = false;
function checkFocus(node) {
const select = node.closest('select');
const update = () => focused = document.activeElement == select;
select.addEventListener('focus', update);
select.addEventListener('blur', update);
update();
return {
destroy() {
select.removeEventListener('focus', update);
select.removeEventListener('blur', update);
},
};
}
</script>
<!-- Makes sure that the currently selected value always exists.
Also provides an entry into the DOM to find the select. -->
<option style:display="none" use:checkFocus>{value}</option>
{#if focused}
{#each list as o}
<option>{o}</option>
{/each}
{/if}
REPL
REPL
There are other ways of doing this, like passing in the focus state, but this way it is a bit more neatly encapsulated.
还有其他方法可以做到这一点,比如传入焦点状态,但这种方式会更巧妙地封装它。
更多回答
It turns out that it was a problem with a Chrome extension that was slowing down the appearance of forms (a password manager that parses each form field apparently). If I disable this extension there is no problem at all. Thanks for the detailed answer with interesting pointers to different improvement techniques.
事实证明,这是Chrome扩展的一个问题,它降低了表单的显示速度(显然是一个解析每个表单域的密码管理器)。如果我禁用这个扩展,根本不会有任何问题。感谢您的详细回答,并提供了不同改进技术的有趣提示。
我是一名优秀的程序员,十分优秀!