目录:
官方解释:介绍key
官方建议:使用key
初步思考:怎样使用key
深度思考:为什么使用key&怎样正确使用key
参考资料
React官方解释:
一些场景下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需要保证和不同批兄弟不一样
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。