Keyed Fragments with Dynamic Children

目錄:

  • 官方解釋:介紹keyhtml

  • 官方建議:使用keynode

  • 初步思考:怎樣使用keyreact

  • 深度思考:爲何使用key&怎樣正確使用keygit

  • 參考資料github

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>
        );
    };

react-map-without-key-without-parent
react-map-without-key-with-parent

這也是一個神奇效果,同級的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>
        );
    };
}

react-without-key
react-with-index-as-key

即便沒有key,或者錯誤使用key,react也能返回react-id,同時有一些警告,固然,錯誤使用key,結果和沒有key是同樣的,具體會有什麼樣的差異,下面會深究

react-warn

  • 對於官方第二個建議,咱們可使用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>
    );
};

效果也是能夠猜到的

react-array-without-key
react-createfragment

  • 咱們甚至能夠作這樣的事情,注意,這裏的data是一個數組,可是渲染出來的節點只有一個。從結果咱們也能夠看出來,當key相同時,節點不會重複渲染。所以得出的結論是,key必須保障惟一性,固然這也是官方的建議。key的惟一性只須要保證在同父級組件下,同頁面無所謂。

render() {
        return (
            <ul>
                {this.state.data.map(function(result) {
                    return <ListItem key='1' data={result}/>;
                })}
            </ul>
        );
    };

react-the-same-key

探究:

上面講了半天,也是週四下午我和劉晶、羅黎討論的主要內容,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>
        );
    };

no-key
index-as-key
unique-key

從結果咱們已經很是容易看出來。當在一列數據的最前放添加數據時,他們對於節點的處理狀況是不一樣的。

  • 不添加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);
    };

change-order-without-key
change-order-with-key

咱們知道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須要保證和不一樣批兄弟不同

相關參考:

react-1
react-2

相關文章
相關標籤/搜索