Html.Keyed
在上一个页面,我们了解了虚拟 DOM 的工作原理,以及我们可以如何使用 Html.Lazy
来避免很多工作。现在,我们将介绍 Html.Keyed
避免更多的工作。
此优化特别有助于界面中必须支持插入、删除和重新排序的数据列表。
问题
假设我们有一个美国总统列表。也许它允许我们按姓名按教育程度、按净资产和按出生地排序。
当 diff 算法(在上一个页面中描述)获取到一个长项目列表时,它将逐一对其进行遍历
- 对比当前第一个元素和下一个第一个元素。
- 对比当前第二个元素和下一个第二个元素。
- ...
但是,当你更改排序顺序时,所有这些元素都将不同!因此,你最终会在 DOM 上做了很多工作,而你本可以只是重新排列一些节点。
插入和删除也存在此问题。假设你删除了 100 个项目中的第一个。所有内容都将偏离一个,并且看起来不同。因此,你得到了 99 个差异和最后一次删除。不好!
解决方案
所有这些问题的解决方法是 Html.Keyed.node
,它可以对每项与一个“键”进行配对,该“键”可以很容易地将它与其他所有项区分开来。
因此,在我们的总统示例中,我们可以编写如下代码
import Html exposing (..)
import Html.Keyed as Keyed
import Html.Lazy exposing (lazy)
viewPresidents : List President -> Html msg
viewPresidents presidents =
Keyed.node "ul" [] (List.map viewKeyedPresident presidents)
viewKeyedPresident : President -> (String, Html msg)
viewKeyedPresident president =
( president.name, lazy viewPresident president )
viewPresident : President -> Html msg
viewPresident president =
li [] [ ... ]
每个子节点都与一个键相关联。因此,我们无需按对进行差异对比,而可以基于匹配键进行差异对比!
现在,Virtual DOM 实现可以识别列表被重新排序时的情况。它首先按 key 将所有总统匹配起来。然后对它们进行 diff。我们为每个条目使用了 lazy
,所以我们可以跳过所有这些工作。太棒了!然后它找出如何随机播放 DOM 节点,按照你想要的方式显示内容。所以带键的版本最终会减少很多工作。
重新排序有助于展示它如何运作,但它并不是真正需要此优化的最常见情况。带键节点对于插入和删除极其重要。当从 100 个元素中删除第 1 个元素时,使用带键节点可以让 Virtual DOM 实现立即识别它。所以你只需执行一次删除,而不是 99 次 diff。
概要
与正常应用程序中发生的计算相比,修改 DOM 极其缓慢。始终首先选择 Html.Lazy
和 Html.Keyed
。我建议尽可能使用分析对其进行验证。一些浏览器提供了程序的时间线视图,比如这个。它可以让你了解加载、脚本编写、渲染、绘制等方面用时概览。如果你看到 10% 的时间用于脚本编写,你的 Elm 代码可能需要加快两倍速度,但不会产生明显差异。而简单添加 lazy 和 keyed 节点,即使修改 DOM 更少,也可能从那 90% 中挖出一大块!