Open SunXinFei opened 6 years ago
可以使用 React DevTools 可视化这些重新渲染的虚拟DOM:
Does "Highlight Updates" trace renders? With React 15 and earlier, "Highlight Updates" had false positives and highlighted more components than were actually re-rendering. Since React 16, it correctly highlights only components that were re-rendered. 安装好这个工具之后,打开Chrome浏览器的开发者工具,可以看到一个React的tab页,在这里面有个勾选。 当与你的页面进行交互,你应该会看到,所有重新渲染的组件周围都会出现高亮显示的边框。 反过来,这可以让你知道没有必要重新渲染的组件。
Note: As of React 16, react-addons-perf is not supported. Please use your browser’s profiling tools to get insight into which components re-render.
在 开发模式 中,你可以在支持相关功能的浏览器中使用性能工具来可视化组件 装载(mount) ,更新(update) 和 卸载(unmount) 的各个过程。例如: 在 Chrome 中操作如下: 通过添加 ?react_perf 查询字段加载你的应用(例如:http://localhost:3000/?react_perf)。 打开 Chrome DevTools Performance 并点击 Record 。( 愚人码头注:如何使用时间轴工具 译文) 执行你想要分析的操作,不要超过20秒,否则 Chrome 可能会挂起。 停止记录。 在 User Timing 标签下,React事件将会分组列出。
PureComponent使用浅比较判断组件是否需要重绘,所以当出现直接对state数据做操作时,数据的修改并不会导致重绘,如下代码:
options.push(new Option())
options.splice(0, 1)
options[i].name = "Hello"
为了避免出现这些问题,推荐使用immutable.js。immutable.js 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。请看下面动画:
其他两个可以帮助我们使用不可变数据的库分别是:seamless-immutable 和 immutability-helper。
不可变数据提供了一种更简单的方式来追踪对象的改变,这正是我们实现 shouldComponentUpdate 所需要的。这将会提供可观的性能提升。
在一些情况下,你的组件可以通过重写生命周期函数 shouldComponentUpdate 来优化性能。该函数会在重新渲染流程前触发。该函数的默认实现中返回的是 true,使得 React 执行更新操作:
shouldComponentUpdate(nextProps, nextState) {
return true;
}
如果你的组件在部分场景下不需要更行,你可以在 shouldComponentUpdate 返回 false 来跳过整个渲染流程,包括调用render() 和之后流程。
render() {
<MyInput onChange={e => this.props.update(e.target.value)} />;
}
以及:
update(e) {
this.props.update(e.target.value);
}
render() {
return <MyInput onChange={this.update.bind(this)} />;
}
注意第二个例子也会导致创建新的函数实例。为了解决这个问题,需要提前绑定this指针:
constructor(props) {
super(props);
this.update = this.update.bind(this);
}
update(e) {
this.props.update(e.target.value);
}
render() {
return <MyInput onChange={this.update} />;
}
2.组件传递数据,传递基本数据结构,避免传递复杂数据结构(数组、对象) 3.Literal Array与Literal Object(其他文章提出,我本人没实验)
{this.props.items.map(i =>
<Cell data={i} options={this.props.options || []} />
)}
若options为空,则会使用[]。[]每次会生成新的Array,因此导致Cell每次的props都不一样,导致需要重绘。解决方法如下:
const default = [];
{this.props.items.map(i =>
<Cell data={i} options={this.props.options || default} />
)}
<CardData dragCardID={-1}
type={c.apptype}
id={c.pk_appregister}
groupID = {groupID}
groupIndex = {index}
key={`${groupID}_${c.pk_appregister}`}
...{c}//这个不要出现
/>
子组件需要什么数据就传递什么数据,而且尽量传递浅层的数据
Calling .bind Within render
class Button extends React.Component {
handleClick() {
console.log('clickity');
}
render() {
return (
<button onClick={this.handleClick.bind(this)}/>
);
}
}
另一种写法,同样Bad
class Button extends React.Component {
handleClick() {
console.log('clickity');
}
render() {
var handleClick = this.handleClick.bind(this);
return (
<button onClick={handleClick}/>
);
}
}
Arrow Function in render,render中使用箭头函数,Bad
class Button extends React.Component {
handleClick() {
console.log('clickity');
}
render() {
return (
<button onClick={() => this.handleClick()}/>
);
}
}
1.Binding in the Constructor
class Button extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('clickity');
}
render() {
return (
<button onClick={this.handleClick}/>
);
}
}
2.Property Initializers
class Button extends React.Component {
// Use an arrow function here:
handleClick = () => {
console.log('clickity');
}
render() {
return (
<button onClick={this.handleClick}/>
);
}
}
在这里我说一下关于这三个功能类似的类库的一些区别和易用性
Facebook 工程师 Lee Byron 花费 3 年时间,与 React 同期出现,但没有被默认放到 React 工具集里(React 提供了简化的 Helper)。它内部实现了一套完整的 Persistent Data Structure,还有很多易用的数据类型。像 Collection、List、Map、Set、Record、Seq。有非常全面的map、filter、groupBy、reduce``find函数式操作方法。同时 API 也尽量与 Object 或 Array 类似。 优点:大而全的API,尤其是Immutable.is() 或者 .equals(),直接替换了===判断,直接hash地址判断,效率不言而喻 缺点:引入项目的代价很大,这里包括需要修改redux,工程文件也很大,min文件50kb,关键是所有的循环遍历不能是简单的循环,而是都需要通过它提供的API才可以获得某个子节点。原因如下图,这是通过FromJS转换后的数据结构:
与 Immutable.js 学院派的风格不同,seamless-immutable 并没有实现完整的 Persistent Data Structure,而是使用 Object.defineProperty(因此只能在 IE9 及以上使用)扩展了 JavaScript 的 Array 和 Object 对象来实现,只支持 Array 和 Object 两种数据类型,API 基于与 Array 和 Object 操持不变。代码库非常小,压缩后下载只有 2K。而 Immutable.js 压缩后下载有 16K。
优点:嵌入项目非常的便捷,因为相对与元数据,数据结构没有大的变化,只是添加了属性,相较于helper,该类库支持下面这种数组下标为变量的情况,变量和key的区别就是加不加引号
Immutable.setIn(groups, [groupIndex,"apps",index,"isChecked"],checked);
数据结构如下图
缺点:API相对于immutable.js略单薄一些,seamless-immutable.js 性能低于 immutable.js。使用shallow copy的方式產生新值來回傳,沒有structural sharing和value equality check所帶來的效能好處。数据嵌套层级越深,数据量越大,性能差异越明显。这里需要根据业务特点来做选择,业务没有大批量的深度数据修改需求,易用性比性能更重要。
也是一个小巧轻便的类库,react-dnd里面有使用 优点:类库小巧轻便,不会改变原数据结构,如下图 缺点:语法糖使用起来不是很舒服,而且最关键的是,想更新数组第N个下标,只能传显性的数字,而不能传递变量,原因是源代码中的forEach所有的key时,变量会成为字符串,而不是解析之后的。
import { createStore, combineReducers, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import Test from './test/reducer';
import thunk from 'redux-thunk';
let store = createStore( combineReducers({ Test }), {}, composeWithDevTools(applyMiddleware(thunk)) ); export default store;
store文件夹中创建一个test文件夹,其中reducer.js内容如下
```js
import * as templateStore from './action-type';
let defaultState = {
text: 'Hello world',
};
export default (state = defaultState, action = {}) => {
return state;
};
调用的地方如下:
import React from 'react'
import { connect } from 'react-redux';
const SourceBox = ({ text }) => {
return (
<div>
{text}
</div>
)
}
export default (connect(
(state) => ({
text: state.Test.text
}),
{}
)(SourceBox))
首先是store中的index.js:
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import Test from './test/reducer';
import { combineReducers } from 'redux-immutable';
import thunk from 'redux-thunk';
import Immutable from 'immutable';
let store = createStore(
combineReducers({ Test }),//注意这里
Immutable.Map({}),//注意这里
composeWithDevTools(applyMiddleware(thunk))
);
export default store;
然后是test中的reducer.js
import * as templateStore from './action-type';
import { fromJS } from 'immutable';
let defaultState = fromJS({//注意这里
text: 'Hello world'
});
export default (state = defaultState, action = {}) => {
return state;
};
调用的地方也需要改造
import React from 'react'
import { connect } from 'react-redux';
const SourceBox = ({ text }) => {
return (
<div>
{text} 、
</div>
)
}
export default (connect(
(state) => ({
text: state.getIn(['Test','text']) //注意这里
}),
{}
)(SourceBox))
如果项目中用到了prop-types需要验证对象/数组数据类型的话则需要
import propTypes from 'prop-types';
Sider.propTypes = {
folderPathLocal: propTypes.instanceOf(List),
folderPathActiveInSider: propTypes.string
};
export default connect(
(state) => ({
folderPathLocal: state.get('folderPathLocal'),
folderPathActiveInSider: state.get('folderPathActiveInSider')
}),
{
addFolderPathForLocal,
setFolderPathActiveInSider
}
)(Sider);
问题描述
最近写的项目,工作桌面拖拽布局,项目中涉及到大量卡片的渲染,卡片分组和卡片都可以拖拽,拖拽和渲染都是耗费性能和内存,当卡片量多的时候,发现会出现内存占用过高和浏览器卡顿的现象,需要进行优化。这个项目旧的版本没有引入redux,为了多个组件之间状态的通信管理方便,后面加入了react-redux,所以拖拽优化的过程也涉及到react-redux的一些tips
优化的小结
优化之前要知道在dev开发模式和实际生产模式,性能本身就有差别,在代码不变的情况下,生产模式下的性能更高。 在项目开发时,除了一些基本的影响性能的写法上注意一下,不必上来就开始谈性能,项目上来就着手优化,这样会导致项目在开始阶段束手束脚。性能出现问题再去解决也不迟。一般情况下,处理好shouldComponentUpdate,不该render的不render,性能会出现明显提升,SCU要写好,不然可能会出现该DOM变化的时候,反而不变化的Bug。
一些原理
React 构建和维护渲染 UI 的内部表示。它包括你从组件中返回的 React 元素。这些内部状态使得React只有在必要的情况下才会创建DOM节点和访问存在DOM节点,因为对JavaScript对象的操作是比DOM操作更快。这被称为”虚拟DOM”,React Native的基于上述原理。 当组件的 props 和 state 更新时,React 通过比较新返回的元素 和 之前渲染的元素 来决定是否有必要更新DOM元素。如果二者不相等,则更新DOM元素。