一、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 算法做了三处优化
- 同层节点之间互相比拟,不会垮节点比拟
- 不同类型的节点,产生不同的树结构
- 开发中,能够通过 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>
参考 react 面试题解答 前端 react 面试题具体解答
三、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 为 ” 大话西游 ” 的元素插入到最后面的地位即可;