New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第 1 题:写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么? #1
Comments
就我的使用来说(Vue)key的作用是为了在数据变化时强制更新组件,以避免“原地复用”带来的副作用。另外,某些情况下不带key可能性能更好,见:issuecomment |
@yeild |
主要是为了提升diff【同级比较】的效率。自己想一下自己要实现前后列表的diff,如果对列表的每一项增加一个key,即唯一索引,那就可以很清楚的知道两个列表谁少了谁没变。而如果不加key的话,就只能一个个对比了。 |
就react而言,key是对于列表组件而言,并且无key或者key不唯一会报错提示 |
@Tarhyru key能提高diff效率其实是不准确的。 见vue/patch.js,在不带key的情况下,判断sameVnode时因为a.key和b.key都是undefined,对于列表渲染来说已经可以判断为相同节点然后调用patchVnode了,实际根本不会进入到答主给的else代码,也就无从谈起“带key比不带key时diff算法更高效”了。 然后,官网推荐推荐的使用key,应该理解为“使用唯一id作为key”。因为index作为key,和不带key的效果是一样的。index作为key时,每个列表项的index在变更前后也是一样的,都是直接判断为sameVnode然后复用。 说到底,key的作用就是更新组件时判断两个节点是否相同。相同就复用,不相同就删除旧的创建新的。 正是因为带唯一key时每次更新都不能找到可复用的节点,不但要销毁和创建vnode,在DOM里添加移除节点对性能的影响更大。所以会才说“不带key可能性能更好”。看下面这个实验,渲染10w列表项,带唯一key与不带key的时间对比: 不使用key的情况:
使用id作为key的情况:
list构造:
因为不带key时节点能够复用,省去了销毁/创建组件的开销,同时只需要修改DOM文本内容而不是移除/添加节点,这就是文档中所说的“刻意依赖默认行为以获取性能上的提升”。 既然如此,为什么还要建议带key呢?因为这种模式只适用于渲染简单的无状态组件。对于大多数场景来说,列表组件都有自己的状态。 举个例子:一个新闻列表,可点击列表项来将其标记为"已访问",可通过tab切换“娱乐新闻”或是“社会新闻”。 不带key属性的情况下,在“娱乐新闻”下选中第二项然后切换到“社会新闻”,"社会新闻"里的第二项也会是被选中的状态,因为这里复用了组件,保留了之前的状态。要解决这个问题,可以为列表项带上新闻id作为唯一key,那么每次渲染列表时都会完全替换所有组件,使其拥有正确状态。 这只是个简单的例子,实际应用会更复杂。带上唯一key虽然会增加开销,但是对于用户来说基本感受不到差距,而且能保证组件状态正确,这应该就是为什么推荐使用唯一id作为key的原因。至于具体怎么使用,就要根据实际情况来选择了。 以上个人见解,如有误望指正。 |
我的理解是,vue和react虽然都采用了diff算法。 但是react本身的设计和vue的设计是截然不同的, vue采用了更加细粒度的更新组件的方式,即给每一个属性绑定监听, 而react是采用自顶而下的更新策略,每次小的改动都会生成一个全新的vdom。从而进行diff,如果不写key,可能就会发生本来应该更新却没有更新的bug。 总结, 更确切的说应该是diff算法在你的 |
@yeild |
@Tarhyru 不是的,我是指如果不带key,则a.key 和 b.key 都是undefined,就直接进入两个节点相同的逻辑,到这里diff已经结束了,根本不会运算到后边'利用对象取值而不是遍历数组'找相同节点的那一步。 从这个角度来说,并没有体现“有key比无key diff算法效率更高这一点”。 @azl397985856 你的补充很好,我提到的“不带key性能更好”,其实是因为两个key都是undefined,自然就相同然后复用组件了,原文有歧义,已做修改。 |
@yeild 2019-03-01 |
@yeild |
Diff算法只在有重新排序(包括中间插入和删除节点)的情况下才可能有优化的作用,因为这样会有已有节点的移动,删除,以及新节点的插入等操作,这种情况下最好是复用以前已经生成的节点。 |
@yeild |
就自己的使用经验而言, 如果不带key, 那么vue会尽量复用dom节点, 不过这样如果列表做了进入动画, 离开动画, 就不会触发了, 因为是直接修改了变化了属性, key的应用一般都是动画相关, 那一块节点重新生成, 这样动画就可以生效了. 给路由配上key也是为了做页面转场动画或者开启一个新的页面(组件)生命周期 |
"原地复用"不产生副作用的情况下,不用key效率最快 “原地复用”产生副作用,需要用key,且用key通过map查找比遍历查找效率更快 用key的主要作用是不产生副作用,跟不用key去比效率就没意义了,不是一个层面上的事,效率较快是和遍历查找相比而言 |
长知识了 |
一、Vue中的key为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有唯一 id <div v-for="item in items" v-bind:key="item.id">
<!-- 内容 -->
</div> 建议尽可能在使用 v-for 时提供 key attribute,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。 key 的特殊属性主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试修复/再利用相同类型元素的算法。使用 key,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。 有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。 参考文章二、React 中的keykey帮助React识别哪些项目已更改,已添加或已删除。应该为数组内部的元素赋予键,以使元素具有稳定的标识: const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
); 参考文章 |
文中提到“v-for的内容会生成以下的dom节点数组,我们给每一个节点标记一个身份id” 楼主,请问如何为每一个节点标记一个身份id? |
谢谢大佬分享 |
为什么是 |
vue和react虽然都采用了diff算法。 但是diff设计是截然不同的, vue采用依赖收集追踪,可以更加细粒度的更新组件,即给模板使用到的每一个属性绑定监听, 而react是采用自顶而下的更新策略,每次小的改动都会生成一个全新的vdom。 不管是什么diff算法,核心都是一样的,key的作用主要是为了高效的更新虚拟DOM列表,key 值是用来判断 VDOM 元素项的唯一依据 。 使用key不保证100%比不使用快,这就和Vdom不保证比操作原生DOM快是一样的,这只是一种权衡,其实对于用index作为key是不推荐的,除非你能够保证他们不会发生变化。这个key要体现唯一,通常推荐使用server给的SQL-ID。通常接口返回的又没有SQL-ID,怎么办呢,又不能用随机数,只能用index代替喽! 推荐使用shortid生成唯一key的数组,和数据数组一起使用,省去提交数据时再重组数组。 案例: import React from 'react';
import shortid from 'shortid';
class Demo extends React.Component {
constructor(props) {
super(props);
this.state = {
data: ['a', 'b', 'c']
}
this.dataKeys = this.state.data.map(v => shortid.generate());
}
deleteOne = index => { // 删除操作
const { data } = this.state;
this.setState({ data: data.filter((v, i) => i !== index) });
this.dataKyes.splice(index, 1);
}
render() {
return (
<ul>
{
data.map((v, i) =>
<li
onClick={i => this.deleteOne(i)}
key={this.dataKeys[i]}
>
{v}
</li>
)
}
</ul>
)
}
} 另外需要指明的是:
React小技巧汇总 |
加 key 是为了特异性识别 如果不加key 就相当于 你的元素里面没有加id 编号 做增删改查 只会默认按照排序索引进行增删改查 然后 就会导致业务逻辑 跟你的试图操作有出入 你会说 咦我想编辑第二个 为何第一个没了 |
其实是要分场景的,以vue的diff算法为例, before <div>1</div>
<div>2</div>
<div>3</div> after <div>2</div>
<div>3</div>
<div>4</div> 不加key的情况下并不会对dom节点进行移动、增删操作,只会更新dom节点的信息,所以性能比较好。 而加key的情况下假设如下所示 <div>1</div><!--key 设置为1-->
<div>2</div><!--key 设置为2-->
<div>3</div><!--key 设置为3--> after <div>2</div><!--key 设置为2-->
<div>3</div><!--key 设置为3-->
<div>4</div><!--key 设置为4--> 加了key的情况下,diff算法为了复用dom节点,会多次进行dom的移动和插入操作。 然后可以换一种场景 before <h1>1</h1>
<div>2</div>
<h2>3</h2> after <span>4</span>
<div>2</div>
<label>6</label> 不加key的情况下,会创建after中的dom元素,再插到 而如果加了key before <h1>1</h1><!--key 设置为1-->
<div>2</div><!--key 设置为2-->
<h2>3</h2><!--key 设置为3--> after <span>4</span><!--key 设置为4-->
<div>2</div><!--key 设置为2-->
<label>6</label><!--key 设置为6--> 那么相比不加key的情况, |
不是列表的key是哪里来的 |
以下是我的理解,如果出现错误,请大神指正! |
看了N个关于vue中的key的用法说明,没有一个通俗易懂,一看就明白的例子,在这总结一下,diff算法的作用为了标记节点(组件),当节点内容或者组件内容有所改变时,它能够快速地找到对应的节点进行复用,eg:排队时,每个人都有自己的名字,一开始是乱的,老师想让俩个人调换位置,直接把俩个人调换一下就行,而不是因为俩个人调换位置把所有人都移动一下 |
简单来说就是在大部分情况下增强复用性,在 diff 两个新老 list 的时候,vue 会采用 |
|
如果不带key, 对比的时候应该是所有节点的位置都没有变化,只不过innerText变化了。带key的情况是innerText不变,节点位置交换 |
就简单dom列表而言,确实不带key的时候只更新了innerText。所以比带key的“创建+删除”节点效率更高。 |
key是dom节点唯一标识,在新旧dom节点渲染时,如果key变化了直接对节点销毁重建, |
有key的情况下,会使用map映射查找节点,没有key时,遍历查找。即,使用map映射能查找到的节点,使用遍历也能查找到。 |
不加key就默认采用“就地复用”策略,加key也是为了复用。大佬,给我解释一下这都是什么跟什么。 |
warning,not error |
报错是eslint的问题, 与为什么用key无关 |
看完了所有的答案,好像都没有一个实际案例来说明没有key的时候带来的问题以及有key的时候是如何解决的,下面给大家跑一个案例: <template>
<ul>
<li v-for="item in list">{{item.name}}</li>
</ul>
<button @click="changeList">删除列表</button>
</template>
<script>
export default {
data () {
return {
list: []
}
},
mounted () {
this.list = Array.from({length: 10}).map((_,index) => {
return {
name: index,
id: index
}
})
this.$nextTick(()=>{
let listNodes = document.getElementsByTagName("li")
listNodes[0].setAttribute("active", true)
})
},
methods: {
changeList () {
this.list.splice(0,1, {
name: 9,
id: 9
})
}
}
}
</script> 在这里有一个列表, 在创建之后被别有用心之人第一项的属性上加了一个 |
react Diffing算法详解render函数执行会产生react元素树,下次render会产生另外一个元素树,react需要对比两个元素树差别,来更新同步真实DOM,使用最简单的广度优先遍历,时间复杂度达到O(n^3)
元素树的更新有以下几种情况:
注意情况
key元素的作用是用来指定当前元素无需删除,只需要原地复用 |
这句话不对吧,原来保留的节点是可以复用的 |
有key: 真定位,强复用 |
有兴趣的朋友可以去试下!每天赚几十元,可以当额外收入~w详情请登陆招聘网站http://hjmqtv.cn/
|
更新 --------------------------
受楼下答案的一些特殊情况影响,导致很多人都认为key不能"提高"diff速度。在此继续重新梳理一下答案。
在楼下的答案中,部分讨论都是基于没有key的情况diff速度会更快。确实,这种观点并没有错。没有绑定key的情况下,并且在遍历模板
简单
的情况下,会导致虚拟新旧节点对比更快,节点也会复用。而这种复用是就地复用
,一种鸭子辩型
的复用。以下为简单的例子:以上的例子,v-for的内容会生成以下的dom节点数组,我们给每一个节点标记一个身份id:
从以上来看,不带有key,并且使用简单的模板,基于这个前提下,可以更有效的复用节点,diff速度来看也是不带key更加快速的,因为带key在增删节点上有耗时。这就是vue文档所说的
默认模式
。但是这个并不是key作用,而是没有key的情况下可以对节点就地复用
,提高性能。这种模式会带来一些隐藏的副作用,比如可能不会产生过渡效果,或者在某些节点有绑定数据(表单)状态,会出现状态错位。VUE文档也说明了
这个默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态 (例如:表单输入值) 的列表渲染输出
楼下 @yeild 也提到,在不带key的情况下,对于
简单列表页渲染
来说diff节点更快是没有错误的。但是这并不是key的作用呀。但是key的作用是什么?
我重新梳理了一下文字,可能这样子会更好理解一些。
1. 更准确
因为带key就不是
就地复用
了,在sameNode函数a.key === b.key
对比中可以避免就地复用的情况。所以会更加准确。2. 更快
利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。(这个观点,就是我最初的那个观点。从这个角度看,map会比遍历更快。)
原答案 -----------------------
vue和react都是采用diff算法来对比新旧虚拟节点,从而更新节点。在vue的diff函数中(建议先了解一下diff算法过程)。
在交叉对比中,当新节点跟旧节点
头尾交叉对比
没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index 的map映射)。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种一个map映射,另一种是遍历查找。相比而言。map映射的速度更快。vue部分源码如下:
创建map函数
遍历寻找
The text was updated successfully, but these errors were encountered: