React中key的必要性与使用
React这个框架的核心思想是,将页面分割成一个个组件,一个组件还可能嵌套更小的组件,每个组件有自己的数据(属性/状态);当某个组件的数据发生变化时,更新该组件部分的视图。更新的过程是由数据驱动的,新的数据自该组件顶层向下流向子组件,每个组件调用自己的render
方法得到新的视图,并与之前的视图作diff-比较差异,完成更新。这个过程就叫作reconciliation-调和。
React通过virtual dom
来实现高效的视图更新。基本原理是用纯js对象模拟dom树,每当更新时,根据组件们的render
方法计算出新的虚拟dom树,并与此前的虚拟dom树作diff,得到一个patch(差异补丁),最后映射到真实dom树上完成视图更新。而两棵树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,很少出现跨越层级移动DOM元素的情况,所以React采用了简化的diff算法,只会对virtual dom中同一个层级的元素进行对比,这样算法复杂度就可以达到 O(n)。
由于React采用的diff算法是对新旧虚拟dom树同层级的元素挨个比较,碰到循环输出的元素时会有一些问题,比如列表。先来看一个例子:
1 2 3 4 5 6 7 8 9 10 11 | / / 旧v - dom <ul> <li>first< / li> <li>second< / li> < / ul> / / 新v - dom <ul> <li>zero< / li> <li>first< / li> <li>second< / li> < / ul> |
React在diff两棵树时,发现原来的两个li元素都与新v-dom中对应位置上的两个li元素不同,就会对其修改,并向真实dom树中插入新的second节点。实际上,我们可能只是进行了在first之前插入新zero节点的操作,而现在进行了额外的修改操作。
React官方文档提示我们应该使用key属性来解决上述问题。key是一个字符串,用来唯一标识同父同层级的兄弟元素。当React作diff时,只要子元素有key属性,便会去原v-dom树中相应位置(当前横向比较的层级)寻找是否有同key元素,比较它们是否完全相同,若是则复用该元素,免去不必要的操作。
延续第一个例子,如果每个li元素都有key属性:
1 2 3 4 5 6 7 8 9 10 11 | / / 旧v - dom <ul> <li key = "1" >first< / li> <li key = "2" >second< / li> < / ul> / / 新v - dom <ul> <li key = "0" >zero< / li> <li key = "1" >first< / li> <li key = "2" >second< / li> < / ul> |
现在React就知道了,新增了key为"0"的元素,而"1"与"2"仅仅移动了位置。
key必须是字符串类型,它的取值可以用数据对象的某个唯一属性,或是对数据进行hash来生成key。
1 2 3 | <ul> { list . map (v = > <li key = {v.idProp}>{v.text}< / li>)} < / ul> |
但是强烈不推荐用数组index来作为key。如果数据更新仅仅是数组重新排序或在其中间位置插入新元素,那么视图元素都将重新渲染。来看下例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <ul>{ list . map ((v,idx) = ><li key = {idx}>{v}< / li>)}< / ul> / / [ 'a' , 'b' , 'c' ] = > <ul> <li key = "0" >a< / li> <li key = "1" >b< / li> <li key = "2" >c< / li> < / ul> / / 数组重排 - > [ 'c' , 'a' , 'b' ] = > <ul> <li key = "0" >c< / li> <li key = "1" >a< / li> <li key = "2" >b< / li> < / ul> |
React发现key为0,1,2的元素的text都变了,将会修改三者的html,而不是移动它们。
渲染同类型元素不带key只会产生性能问题,如果渲染的是不同类型的状态性组件,组件将会被替换,状态丢失。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Box extends React.Component { constructor(p) { super (p) this.state = { type : true} this.handler = this.handler.bind(this) } handler() { this.setState({ type : !this.state. type }) } render() { return (<div> <button onClick = {this.handler}>haha< / button> {this.state. type ? (<div><Son_1 / ><Son_2 / >< / div>) : (<div><Son_2 / ><Son_1 / >< / div>) } < / div>) } } |
如上述代码,每次按下按钮,原Son_1与Son_2组件的实例都将被销毁,并创建新的Son_1与Son_2实例,不能继承原来的状态;而它们实际上只是调换了位置。给它们加上key可以避免问题:
1 2 3 4 | {this.state. type ? (<div><Son_1 key = "1" / ><Son_2 key = "2" / >< / div>) : (<div><Son_2 key = "2" / ><Son_1 key = "1" / >< / div>) } |
所以,碰到数组->列表的映射,或是同级元素需要移位的情况,一定要给元素加上key属性!
结论:用key可以提升react的性能,但是在很多地方维护key会增大复制操作业务的复杂程度,需根据情况权衡
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!