官方解释:介绍keyhtml
官方建议:使用keynode
初步思考:怎样使用keyreact
深度思考:为何使用key&怎样正确使用keygit
参考资料github
一些场景下React中须要key属性来识别一个组件,key属性自己没法在组件的任何位置获取到,而key只须要在组件的兄弟中惟一,而无需全局惟一。算法
在react引用的算法中包含Levenshtein distance
,编辑距离,指两个字符串之间,由一个转成另外一个所须要最好编辑操做次数,可进行的操做包括将一个字符替换成另外一个字符、插入字符、删除字符。数组
经过key来匹配子元素,可让react进行插入、删除、替换、移动都在O(n)内进行。浏览器
The key should always be supplied directly to the components in the array, not to the container HTML child of each component in the array:缓存
// WRONG! var ListItemWrapper = React.createClass({ render: function() { return <li key={this.props.data.id}>{this.props.data.text}</li>; } }); var MyComponent = React.createClass({ render: function() { return ( <ul> {this.props.results.map(function(result) { return <ListItemWrapper data={result}/>; })} </ul> ); } }); // Correct :) var ListItemWrapper = React.createClass({ render: function() { return <li>{this.props.data.text}</li>; } }); var MyComponent = React.createClass({ render: function() { return ( <ul> {this.props.results.map(function(result) { return <ListItemWrapper key={result.id} data={result}/>; })} </ul> ); } });
You can also key children by passing a ReactFragment object.网络
//Not Good var Swapper = React.createClass({ propTypes: { // `leftChildren` and `rightChildren` can be a string, element, array, etc. leftChildren: React.PropTypes.node, rightChildren: React.PropTypes.node, swapped: React.PropTypes.bool } render: function() { var children; if (this.props.swapped) { children = [this.props.rightChildren, this.props.leftChildren]; } else { children = [this.props.leftChildren, this.props.rightChildren]; } return <div>{children}</div>; } }); //Right if (this.props.swapped) { children = React.addons.createFragment({ right: this.props.rightChildren, left: this.props.leftChildren }); } else { children = React.addons.createFragment({ left: this.props.leftChildren, right: this.props.rightChildren }); }
It is often easier and wiser to move the state higher in component hierarchy
正常状况下咱们并不须要使用key,react能够给全部的Stateful Children生成惟一的react-id,而且过程至关智能,以用于以后的dom diff等功能。
render() { return ( <div> {this.state.data.map(function(result, index) { return <DivItem data={result}/>; })} <div></div> <div></div> </div> ); }; render() { return ( <div> <div> {this.state.data.map(function(result, index) { return <DivItem data={result}/>; })} </div> <div></div> <div></div> </div> ); };
这也是一个神奇效果,同级的div节点,经过循环产生的react-id和其余的并非同级效果,不过仔细观察数据,也是很好理解的。观察react-id咱们也能够发现,在没有外包直接父级别节点的状况下,经过循环产生的节点应该会有一个“虚拟父节点”,这时候的react-id和同层次的节点已经不同,同时又不一样于真正存在父节点的状况
对官方的第一个建议,这个很好理解,key自己用在父组件中来咱们看一下相关测试效果
// Wrong class ListItem extends Component { ··· render () { return ( <li key={this.props.data.id}>{this.props.data.text}</li> ); } } class HelloDemo extends Component { ··· render() { return ( <ul> {this.state.data.map(function(result) { return <ListItem data={result}/>; })} </ul> ); }; } // Right class ListItem extends Component { ··· render () { return ( <li>{this.props.data.text}</li> ); } } class HelloDemo extends Component { ··· render() { return ( <ul> {this.state.data.map(function(result) { return <ListItem key={result.id} data={result}/>; })} </ul> ); }; }
即便没有key,或者错误使用key,react也能返回react-id,同时有一些警告,固然,错误使用key,结果和没有key是同样的,具体会有什么样的差异,下面会深究
对于官方第二个建议,咱们可使用ReactFragment,效果以下
// Wrong render() { let line = [<span>道士下山</span>, <span>捉妖记</span>]; return ( <ul> {line} </ul> ); } // Right import Addons from "react/addons"; ... render() { let line = Addons.addons.createFragment({ daoshi: <span>道士下山</span>, zhuoyao: <span>捉妖记</span> }); return ( <ul> {line} </ul> ); };
效果也是能够猜到的
咱们甚至能够作这样的事情,注意,这里的data是一个数组,可是渲染出来的节点只有一个。从结果咱们也能够看出来,当key相同时,节点不会重复渲染。所以得出的结论是,key必须保障惟一性,固然这也是官方的建议。key的惟一性只须要保证在同父级组件下,同页面无所谓。
render() { return ( <ul> {this.state.data.map(function(result) { return <ListItem key='1' data={result}/>; })} </ul> ); };
上面讲了半天,也是周四下午我和刘晶、罗黎讨论的主要内容,key无疑会影响到react-id的生成方式,但咱们关心的是这到底有什么影响??咱们之后写代码要注意什么??
首先,key的出现,是变化的节点,也就是Dynamic Children,咱们考虑到这样的使用状况,每每是须要对节点结构进行增、删、改、查的功能,那么key会对节点产生什么样的影响?
//公用方法,在最前面添加一个节点 handleClick() { let data = this.state.data; data.unshift({id:10, text: '盗梦空间'}); this.setState(data); }; //不添加key render() { return ( <ul onClick={this.handleClick.bind(this)}> {this.state.data.map(function(result, index) { return <ListItem data={result}/>; })} </ul> ); }; //key为index render() { return ( <ul onClick={this.handleClick.bind(this)}> {this.state.data.map(function(result, index) { return <ListItem key={index} data={result}/>; })} </ul> ); }; //key为特定惟一值 render() { return ( <ul onClick={this.handleClick.bind(this)}> {this.state.data.map(function(result, index) { return <ListItem key={result.id} data={result}/>; })} </ul> ); };
从结果咱们已经很是容易看出来。当在一列数据的最前放添加数据时,他们对于节点的处理状况是不一样的。
不添加key的状况下,react自动生成key,长度为n的数组前添加一个数据,在dom结构上将会引起n词内容替换和一次插入(可是被插入的节点实际上是最后一个节点)
key为index的状况和第一种相似,由于数组的index自己发生了改变
key为一个unique值,react将会直接在最前方调用一次插入,显然这样的效率是最高的。
// 替换部分 handleClick() { let data = this.state.data; [data[0], data[3]] = [data[3], data[0]]; this.setState(data); };
咱们知道react当中,即便是相同的组件,当key发生改变的时候,React也会直接跳过Dom diff,彻底弃置以前组件的全部子元素,从头从新开始渲染。上面的例子其实引出一点小问题,若是交换元素的时候,仅仅替换内容是否是更快?也就是前者的替换方式可能要优于后者?
这里想出三个解释:
不少状况下,在第一种方式中,React对元素进行diff操做后会肯定最高效的操做是改变其中元素的属性值,而这样的操做是很是低效的,同时可能致使浏览器查询缓存,甚至致使网络新请求。然后面一种状况证实,移动DOM节点是最高效的作法。参考官网的例子图(下方有)也能够看出
在以前的插入和删除操做中,若是元素是带有属性的,你改变了靠前的属性,其后全部的节点都会受到影响
最重要的一点,在第一种状况下,react认为节点其实并无改变,仅仅是帮助你改变了节点的内容,也就是说,若是你用第一种方式,而且从新给出四个数据,react内部会认为这四个节点和以前的节点是同样的节点,只是将内容替换掉了。可是须要注意Component doesn’t have initial state defined but the previous one,你对以前那四个节点作的事情,极可能会影响到新的四个节点。这些节点并非Stateful Children,而是Dynamic Children,咱们须要的就是react可以彻底替换节点,从新渲染。
Dynamic Children须要使用key
key须要在父节点调用处定义
key不须要保证和非兄弟相异
key须要保证和同批兄弟不同
key须要保证和不一样批兄弟不同