关于react.js:React循环DOM时为什么需要添加key

16次阅读

共计 2607 个字符,预计需要花费 7 分钟才能阅读完成。

一、React 渲染流程和更新流程

  • react 渲染流程:jsx -> 虚构 dom -> 实在 dom
  • react 更新流程:props/state 扭转 -> render 函数从新执行 -> 生成新的虚构 dom 树 -> 新旧虚构 dom 树进行 diff -> 计算出差别进行更新 -> 更新到实在的 dom 树

所以在每次更新的时候,React 须要基于这两颗不同的树之间的差异来判断如何无效的更新 UI,如果一棵树参考另外一棵树进行齐全比拟更新,那么即便是最先进的算法,该算法的复杂程度为 O(n3),其中 n 是树中元素的数量,如果在 React 中应用了该算法,那么展现 1000 个元素所须要执行的计算量将在十亿的量级范畴,这个开销太过低廉了,React 的更新性能会变得十分低效;于是 React 对这个算法进行了优化,将其优化成了 O(n),这也就是传说中的 diff 算法,

二、diff 算法

diff 算法做了三处优化

  1. 同层节点之间互相比拟,不会垮节点比拟
  2. 不同类型的节点,产生不同的树结构
  3. 开发中,能够通过 key 来指定哪些节点在不同的渲染下保持稳定

2-1 比照不同类型的元素

  • 当节点为不同的元素,React 会装配原有的树,并且建设起新的树:当一个元素从 <a> 变成 <img>,从<Article> 变成 <Comment>,或从<Button> 变成 <div> 都会触发一个残缺的重建流程
  • 当卸载一棵树时,对应的 DOM 节点也会被销毁,组件实例将执行 componentWillUnmount() 办法;
  • 当建设一棵新的树时,对应的 DOM 节点会被创立以及插入到 DOM 中,组件实例将执行 componentWillMount()办法,紧接着 componentDidMount() 办法
  • 比方上面的代码更改:React 会销毁 Comment 组件并且从新装载一个新的组件,而不会对 Counter 进行复用;
<div>
    <Comment />
</div>

<span>
    <Comment />
</span>

2-2 比照同一类型的元素

  • 当比对两个雷同类型的 React 元素时,React 会保留 DOM 节点,仅比对及更新有扭转的属性
  • 比方上面的代码更改:通过比对这两个元素,React 晓得只须要批改 DOM 元素上的 className 属性
<div className="before" title="stu" />
<div className="after" title="stu" />
  • 比方上面的代码更改:当更新 style 属性时,React 仅更新有所更变的属性。通过比对这两个元素,React 晓得只须要批改 DOM 元素上的 color 款式,无需批改 fontWeight。
<div style={{color="red",fontWeight:"bold"}} />
<div style={{color="green",fontWeight:"bold"}} />
  • 如果是同类型的组件元素:组件会放弃不变,React 会更新该组件的 props,并且调用 componentWillReceiveProps() 和 componentWillUpdate() 办法,下一步调用 render() 办法,diff 算法将在之前的后果以及新的后果中进行递归;

2-3 对子节点递归

  • 在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差别时,生成一个 mutation(扭转)。
  • 如果在最初插入一条数据的状况:后面两个比拟是完全相同的,所以不会产生 mutation, 最初一个比拟,产生一个 mutation,将其插入到新的 DOM 树中即可,然而如果是在后面插入一条数据,React 会对每一个子元素产生一个 mutation,而不是放弃 <li>星际穿梭 </li><li>盗梦空间 </li> 的不变;这种低效的比拟形式会带来肯定的性能问题,所以就得应用 key 来优化
前面插一条数据
<ul>
     <li> 星际穿梭 </li>
     <li> 盗梦空间 </li>
 </ul>
 <ul>
     <li> 星际穿梭 </li>
     <li> 盗梦空间 </li>
     <li> 大话西游 </li>
 </ul>

 后面插一条数据
 <ul>
     <li> 星际穿梭 </li>
     <li> 盗梦空间 </li>
 </ul>
 <ul>
     <li> 大话西游 </li>
     <li> 星际穿梭 </li>
     <li> 盗梦空间 </li>
 </ul>

参考 前端进阶面试题具体解答

三、key

要切记,在 diff 算法中,能够通过 key 来指定哪些节点在不同的渲染下保持稳定,并且要保障 key 是惟一的,不要应用随机数(随机数在下一次 render 时,会从新生成一个数字),也不能应用 index,这都对性能是没有优化的

import React, {Component} from "react";

export default class App extends Component {constructor(props) {super(props);

    this.state = {movies: ["星际穿梭", "盗梦空间"],
    };
  }

  render() {
    return (
      <div>
        <h2> 电影列表 </h2>
        <ul>
          {this.state.movies.map((item, index) => {return <li key={item}>{item}</li>;          })}        </ul>
        <button onClick={(e) => this.insertMovie()}> 增加电影 </button>
      </div>
    );
  }

  insertMovie() {
    this.setState({movies: ["大话西游", ...this.state.movies],
    });
  }
}

代码解析:在默认条件下,当递归 DOM 节点的子元素时,React 会同时遍历两个子元素的列表;当产生差别时,生成一个 mutation。如果在 movies 前面增加数据,后面两个比拟是完全相同的,所以不会产生 mutation;最初一个比拟,产生一个 mutation,将其插入到新的 DOM 树中即可;
如果在 movies 后面增加数据,React 会对每一个子元素产生一个 mutation,而不是放弃 <li>星际穿梭 </li><li>盗梦空间 </li> 的不变,这种低效的比拟形式会带来肯定的性能问题,
当子元素 (这里的 li) 领有 key 时,React 应用 key 来匹配原有树上的子元素以及最新树上的子元素:
在上面这种场景下,key 为 ” 星际穿梭 ” 和 ” 盗梦空间 ” 的元素仅仅进行位移,不须要进行任何的批改;将 key 为 ” 大话西游 ” 的元素插入到最后面的地位即可;

正文完
 0