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

相关文章
相关标签/搜索