官方解釋:介紹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須要保證和不一樣批兄弟不同