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樹同層級的元素挨個比較,碰到循環輸出的元素時會有一些問題,比如列表。先來看一個例子:

// 舊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屬性:

// 舊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。

<ul>
    {list.map(v=> <li key={v.idProp}>{v.text}</li>)}
</ul>

  

但是強烈不推薦用數組index來作為key。如果數據更新僅僅是數組重新排序或在其中間位置插入新元素,那么視圖元素都將重新渲染。來看下例子:

<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只會產生性能問題,如果渲染的是不同類型的狀態性組件,組件將會被替換,狀態丟失。

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可以避免問題:

{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會增大復制操作業務的復雜程度,需根據情況權衡


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM