千家信息网

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

发表于:2024-11-14 作者:千家信息网编辑
千家信息网最后更新 2024年11月14日,这篇文章给大家分享的是有关html列表中的key属性有什么用的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。diff 算法的时间复杂度是 O(n), 它的实现是基于以下两个假
千家信息网最后更新 2024年11月14日html列表中的key属性有什么用

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

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

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

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

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

如果没有 key 属性

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

  • Duke
  • Villanova

  • Connecticut
  • Duke
  • Villanova

例如,当在子元素列表之前添加一个元素的时候,如果没有 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) =>

  • {todo.text}

  • );

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

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

    key={post.id}

    id={post.id}

    title={post.title} />

    );

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

    0