温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

html列表中的key属性有什么用

发布时间:2022-03-03 11:28:39 来源:亿速云 阅读:1665 作者:小新 栏目:web开发

这篇文章给大家分享的是有关html列表中的key属性有什么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

diff 算法的时间复杂度是 O(n), 它的实现是基于以下两个假设:

两个 type 不同的元素生成不同的 DOM 树;

在两次渲染中,React 能通过 key 属性判断哪一个子元素保持不变;

本文内容围绕「假设 2」展开说明 diff 算法是怎么做到 O(n) 的时间复杂度的。

如果没有 key 属性

当遍历子元素列表的时候,React 会同时遍历新旧子元素列表,一旦遇到两个列表有不同的地方,就会生成一个 mutation。

<ul>

  <li>Duke</li>

  <li>Villanova</li>

</ul>

<ul>

  <li>Connecticut</li>

  <li>Duke</li>

  <li>Villanova</li>

</ul>

例如,当在子元素列表之前添加一个元素的时候,如果没有 key 属性,React 会同时遍历新旧子元素数组:

第一个 li 标签有变化,更新;

第二个 li 标签有变化,更新;

最后插入第三个 li 标签;

这个算法的时间复杂度是 O(n),但是会导致很多不必要的 DOM 操作,性能低下。如果通过一种列表对比算法能避免掉不必要的 DOM 操作,就能优化性能。

Levenshtein Distance 算法

为了使用算法优化性能,让我们对上面的问题做一个抽象,旧的子元素列表:

[1, 2, 3, 4, 5] 

进行了一系列 DOM 节点的删除、插入、修改的操作之后,得到新的列表:

[6, 3, 1, 2, 5, 4] 

知道了新旧的顺序求最小的插入、删除、修改操作,这是数组的最小编辑距离问题。最常见的是 Levenshtein Distance 算法。

举一个例子:求 beaut( [公式] ) 和 ebau( [公式] ) 的最小编辑距离,其中 [公式] 是未知字符。动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。定义长度为「i - 1」的字符串 str1 和长度为「j - 1」的字符串 str2 的最小编辑距离为 [公式] ,那么 beaut( [公式] )和 ebau( [公式] ) 的最小编辑记 [公式] ,[公式] 能且只能基于下面三个状态:

「状态 1」 :beaut( [公式] ) 和 ebau 的最小编辑距离已知,记为 [公式] ;

「状态 2」:beaut 和 ebau( [公式] ) 的最小编辑距离已知,记为 [公式] ;

「状态 3」:beaut 和 ebau 的最小编辑距离已知,记为 [公式] ;

[公式] 和三个状态之间的关系分别为下面三个公式:

[公式]

[公式]

[公式]

解释一下第三个公式,在 [公式] 的情况下, [公式] ;如果 [公式] ,那么 [公式] 。那么上面三个公式值最小的那个就是 [公式] 的解。对照着下图看,也就是 [公式] 的值可以通过其上方,左边,左上对角线的值确定,公式如下:

[公式]

[公式]

[公式]

function minDistance(s1, s2) {

    const len1 = s1.length

    const len2 = s2.length

    let matrix = []

    for (let i = 0; i <= len1; i++) {

        // 构造二维数组

        matrix[i] = new Array()

        for (let j = 0; j <= len2; j++) {

            // 初始化

            if (i == 0) {

                matrix[i][j] = j

            } else if (j == 0) {

                matrix[i][j] = i

            } else {

                // 进行最小值分析

                let cost = 0

                if (s1[i - 1] != s2[j - 1]) { // 相同为0,不同置1

                    cost = 1

                }

                const temp = matrix[i - 1][j - 1] + cost

                matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, temp)

            }

        }

    }

    return matrix[len1][len2] //返回右下角的值

}

从上面的代码可以看出,LD 算法有两个嵌套的 for 循环,所以时间复杂度是 O(n*m),其中 n 为 str1 的长度,m 为 str2 的长度。如果采用 LD 算法,虽然能避免掉不必要的 DOM 操作,但是 diff 算法的时间复杂度就达不到线性了。

key 属性的引入思路

直接应用 LP 算法不能解决我们的问题。我们并不需要找到真正的最小编辑距离,而是需要找到一种算法,这个算法的时间复杂度必须是 O(n),并且能避免掉大部分的 DOM 操作,而前端的列表大部分的操作是子元素的移动。通过引入 key 属性唯一标识子元素,我们可以把最小编辑距离转化成:key 属性是对这个元素的唯一标识,在这个条件下,求新旧子元素列表的最小插入、删除、移动操作。这是一种启发式算法。

旧的子元素列表:

[{ key: 1, val: 1 }, { key: 2, val: 2 }, { key: 3, val: 3 }]

新的子元素列表:

[{ key: 4, val: 4 }, { key: 1, val: 1 }, { key: 3, val: 3 }, { key: 2, val: 2 }]

React 子元素近似最小编辑距离算法

为了寻找上面两个数组的近似最小编辑距离,React 的做法为,正向遍历新的子元素,用新的子元素的 key 值去旧的子元素中查找,如果没找到,就做插入;如果找到了就做移动操作;如果遇到旧的子元素在新的列表中找不到的情况,删除旧的子元素。算法的时间复杂度是 O(n)。 举一个例子,如下图,上一排是旧的子元素列表,下一排是新的子元素列表:

代码如下,正向遍历 nextChildren:

「元素 6」: 是新增元素,新增到 index = 0 的位置;

「元素 3」: 不变;

「元素 1」: 元素 1 在原数组中的位置在元素 3 之前,所以需要移动到元素 3 的后面;

「元素 2」: 元素 2 在原数组中的位置也位于 3 之前,移动到元素 1 的后面;

「元素 5」: 不变;

「元素 4」: 移动到 5 后面;

updateChildren() {

  // find removed

  const removedNodes = findRemoved();

  var updates = [];

  var lastIndex = 0;

  var nextIndex = 0;

  var lastPlacedNode = null;

  for (const name in nextChildren) {

    var prevChild = prevChildren && prevChildren[name];

    var nextChild = nextChildren[name];

    if (prevChild === nextChild) {

      // 移动子元素

      if (prevChild._mountIndex < lastIndex) {

        updates.push({

          type: "MOVE_EXISTING",

          fromIndex: prevChild._mountIndex,

          toIndex: nextIndex,

          afterNode: afterNode

        })

      }

      lastIndex = Math.max(prevChild._mountIndex, lastIndex);

      prevChild._mountIndex = nextIndex;

    } else {

      if (prevChild) {

        lastIndex = Math.max(prevChild._mountIndex, lastIndex);

      }

      // 新增子元素

      updates.push({

        type: "INSERT_MARKUP",

        toIndex: nextIndex,

        content: nextChild

      });

    }

    nextIndex++;

    lastPlacedNode = getNativeNode(nextChild); // 获取 nextChild 对应的 DOM 节点

  }

  // 删除

  for (const name in removedNodes) {

    updates.push({

      type: "REMOVE_NODE",

      content: null,

      fromIndex: prevChildren[name]._mountIndex,

      fromNode: removedNodes[name]

    })

  }

}

和 Vue 的比较

Vue 对子元素的 diff 的思路和 React 一样,都引入了「key 是子元素的唯一标识」这一先决条件。在引入这一条件后,比 React 做了更多的优化,Vue 是从新老子元素列表的两头向中间遍历,并多做了一些特殊判断。就列表更新这一块,Vue 的性能高于 React。而在 Fiber 架构中,由于没有反向指针,React 不容易做到通过双向遍历优化子元素 diff 算法。

key 属性最佳实践

key 属性帮助 React 识别哪些元素改变了,哪些元素是新增的,哪些被删除了。元素列表中的元素的 key 值应该是稳定的,能起到唯一标识的作用。下面列举一些最佳实践:

1. 使用能在子元素列表之间能唯一标识这个子元素的字符串做为其 key 属性。用数组的 index 做为 key 值是一种反模式,需要避免。

const todoItems = todos.map((todo) =>

  <li key={todo.id}>

    {todo.text}

  </li>

);

2. key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key 属性的值,请用其他属性名显式传递这个值。

const content = posts.map((post) =>

  <Post

    key={post.id}

    id={post.id}

    title={post.title} />

);

感谢各位的阅读!关于“html列表中的key属性有什么用”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI