乐趣区

关于react.js:React性能优化

前言
React 是 Facebook 开发的构建用户界面的类库. 它从设计之初就将性能作为重点, 在应用时更是能够采取一些策略而后咱们网站性能更加优化,以下是我平时用到的一些优化形式,心愿能够帮忙到大家!

Code Splitting
Code Splitting 能够帮你“懒加载”代码,如果你没方法间接缩小利用的体积,那么无妨尝试把利用从单个 bundle 拆分成单个 bundle + 多份动静代码的模式。webpack 提供三种代码拆散办法,详情见 webpack 官网

1. 入口终点:应用 entry 配置手动地拆散代码。

2. 避免反复:应用 SplitChunks 去重和拆散 chunk。

3. 动静导入:通过模块的内联函数调用来拆散代码。

在此,次要理解一下第三种动静导入的办法。

1、例如能够把上面的 import 形式

import {add} from ‘./math’;
console.log(add(16, 26));
复制代码
改写成动静 import 的模式,让首次加载时不去加载 math 模块,从而缩小首次加载资源的体积。

import(“./math”).then(math => {
console.log(math.add(16, 26));
});
复制代码
2、例如援用 react 的高阶组件 react-loadable 进行动静 import。

import Loadable from ‘react-loadable’;
import Loading from ‘./loading-component’;

const LoadableComponent = Loadable({
loader: () => import(‘./my-component’),
loading: Loading,
});

export default class App extends React.Component {
render() {

return <LoadableComponent/>;

}
}
复制代码
下面的代码在首次加载时,会先展现一个 loading-component,而后动静加载 my-component 的代码,组件代码加载结束之后,便会替换掉 loading-component

shouldComponentUpdate 防止反复渲染
当一个组件的 props 或者 state 扭转时,React 通过比拟新返回的元素和之前渲染的元素来决定是否有必要更新理论的 DOM。当他们不相等时,React 会更新 DOM。

在一些状况下,你的组件能够通过重写这个生命周期函数 shouldComponentUpdate 来晋升速度,它是在从新渲染过程开始前触发的。这个函数默认返回 true,可使 React 执行更新。

为了进一步阐明问题,援用官网的图解释一下,如下图(SCU 示意 shouldComponentUpdate,绿色示意返回 true(须要更新),红色示意返回 false(不须要更新);vDOMEq 示意虚构 DOM 比对,绿色示意统一(不须要更新),红色示意产生扭转(须要更新))

依据渲染流程,首先会判断 shouldComponentUpdate(SCU)是否须要更新。如果须要更新,则调用组件的 render 生成新的虚构 DOM,而后再与旧的虚构 DOM 比照(vDOMEq),如果比照统一就不更新,如果比照不同,则依据最小粒度扭转去更新 DOM;如果 SCU 不须要更新,则间接放弃不变,同时其子元素也放弃不变。

C1 根节点,绿色 SCU、红色 vDOMEq,示意须要更新。

C2 节点,红色 SCU,示意不须要更新,同时 C4、C5 作为其子节点也不须要查看更新。

C3 节点,绿色 SCU、红色 vDOMEq,示意须要更新。

C6 节点,绿色 SCU、红色 vDOMEq,示意须要更新。

C7 节点,红色 SCU,示意不须要更新。

C8 节点,绿色 SCU,示意 React 须要渲染这个组件;

绿色 vDOMEq,示意虚构 DOM 统一,不更新 DOM。

因而,咱们能够通过依据本人的业务个性,重载 shouldComponentUpdate,只在确认实在 DOM 须要扭转时,再返回 true。个别的做法是比拟组件的 props 和 state 是否真的发生变化,如果发生变化则返回 true,否则返回 false。援用官网的案例。

class CounterButton extends React.Component {
constructor(props) {

super(props);
this.state = {count: 1};

}

shouldComponentUpdate(nextProps, nextState) {

if (this.props.color !== nextProps.color) {return true;}
if (this.state.count !== nextState.count) {return true;}
return false;

}

render() {

return (
  <button
    color={this.props.color}
    onClick={() => this.setState(state => ({count: state.count + 1}))}>
    Count: {this.state.count}
  </button>
);

}
}
复制代码
在以上代码中,shouldComponentUpdate 只查看 props.color 和 state.count 的变动。如果这些值没有变动,组件就不会更新。当你的组件变得更加简单时,你能够应用相似的模式来做一个“浅比拟”,用来比拟属性和值以断定是否须要更新组件。这种模式非常常见,因而 React 提供了一个辅助对象来实现这个逻辑 – 继承自 React.PureComponent。

大部分状况下,你能够应用 React.PureComponent 而不用写你本人的 shouldComponentUpdate,它只做一个浅比拟。然而当你比拟的指标为援用类型数据,浅比拟会疏忽属性或状态渐变的状况,此时你不能应用它,此时你须要关注上面的不可渐变数据。

附:数据渐变(mutated)是指变量的援用没有扭转(指针地址未扭转),然而援用指向的数据产生了变动(指针指向的数据产生变更)。例如 const x = {foo:’foo’}。x.foo=’none’ 就是一个渐变。

应用不可渐变数据结构
援用官网中的例子解释一下渐变数据产生的问题。例如,假如你想要一个 ListOfWords 组件来渲染一个逗号分隔的单词列表,并应用一个带了点击按钮名字叫 WordAdder 的父组件来给子列表增加一个单词。以下代码并不正确:

class ListOfWords extends React.PureComponent {
render() {

return <div>{this.props.words.join(',')}</div>;

}
}

class WordAdder extends React.Component {
constructor(props) {

super(props);
this.state = {words: ['marklar']
};
this.handleClick = this.handleClick.bind(this);

}

handleClick() {

// 这段内容将会导致代码不会依照你预期的后果运行
const words = this.state.words;
words.push('marklar');
this.setState({words: words});

}

render() {

return (
  <div>
    <button onClick={this.handleClick} />
    <ListOfWords words={this.state.words} />
  </div>
);

}
}
复制代码
导致代码无奈失常工作的起因是 PureComponent 仅仅对 this.props.words 的新旧值进行“浅比拟”。在 words 值在 handleClick 中被批改之后,即便有新的单词被增加到数组中,然而 this.props.words 的新旧值在进行比拟时是一样的(援用对象比拟),因而 ListOfWords 始终不会产生渲染。防止此类问题最简略的形式是防止应用值可能会渐变的属性或状态,如:

1、数组应用 concat, 对象应用 Object.assign()

handleClick() {
this.setState(prevState => ({

words: prevState.words.concat(['marklar'])

}));
}
复制代码
// 假如咱们有一个叫 colormap 的对象,上面办法不净化原始对象
function updateColorMap(colormap) {
return Object.assign({}, colormap, {right: ‘blue’});
}
复制代码
2、ES6 反对数组或对象的 spread 语法

handleClick() {
this.setState(prevState => ({

words: [...prevState.words, 'marklar'],

}));
};
复制代码
function updateColorMap(colormap) {
return {…colormap, right: ‘blue’};
}
复制代码
3、应用不可渐变数据 immutable.js

immutable.js 使得变动跟踪很不便。每个变动都会导致产生一个新的对象,因而咱们只需查看索引对象是否扭转。

const SomeRecord = Immutable.Record({foo: null});
const x = new SomeRecord({foo: ‘bar’});
const y = x.set(‘foo’, ‘baz’);
x === y; // false
复制代码
在这个例子中,x 渐变后返回了一个新的索引,因而咱们能够平安的确认 x 被扭转了。不可渐变的数据结构帮忙咱们轻松的追踪对象变动,从而能够疾速的实现 shouldComponentUpdate。

组件尽可能的进行拆分、解耦
组件尽可能的细分,比方一个 input+list 组件,能够将 list 分成一个 PureComponent,只在 list 数据变动时更新。否则在 input 值变动页面从新渲染的时候,list 也须要进行不必要的 DOM diff。

列表类组件优化
key 属性在组件类之外提供了另一种形式的组件标识。通过 key 标识,在组件产生增删改、排序等操作时,能够依据 key 值的地位间接调整 DOM 程序,通知 React 防止不必要的渲染而防止性能的节约。

var items = sortBy(this.state.sortingAlgorithm, this.props.items);
return items.map(function(item){
return <img src={item.src} />
});
复制代码
当程序产生扭转时,React 会对元素进行 diff 操作,并改 img 的 src 属性。显示,这样的操作效率是非常低的。这时,咱们能够为组件增加一个 key 属性以惟一的标识组件:

return <img src={item.src} key={item.id} />
复制代码
减少 key 后,React 就不是 diff,而是间接应用 insertBefore 操作挪动组件地位,而这个操作是挪动 DOM 节点最高效的方法。

bind 函数
绑定 this 的形式:个别有上面 3 种形式:

1、constructor 绑定

constructor(props) {

super(props);
this.handleClick = this.handleClick.bind(this); // 构造函数中绑定

}
// 而后能够
<p onClick={this.handleClick}>
复制代码
2、应用时绑定

<p onClick={this.handleClick.bind(this)}>
复制代码
3、应用箭头函数

<Test click={() => { this.handleClick() }}/>
复制代码
以上三种办法,

第一种最优。因为第一种构造函数只在组件初始化的时候执行一次。

第二种组件每次 render 都会执行。

第三种在每一次 render 时候都会生成新的箭头函数。例:Test 组件的 click 属性是个箭头函数,组件从新渲染的时候 Test 组件就会因为这个新生成的箭头函数而进行更新,从而产生 Test 组件的不必要渲染。

不要滥用 props
props 尽量只传须要的数据,防止多余的更新,尽量避免应用{…props}

ReactDOMServer 进行服务端渲染组件
为了用户会更疾速地看到残缺渲染的页面,能够采纳服务端渲染技术,在此理解一下 ReactDOMServer。

为了实现 SSR,你可能会用 nodejs 框架(Express、Hapi、Koa)来启动一个 web 服务器,接着调用 renderToString 办法去渲染你的根组件成为字符串,最初你再输入到 response。

// using Express
import {renderToString} from “react-dom/server”;
import MyPage from “./MyPage”;
app.get(“/”, (req, res) => {
res.write(“<!DOCTYPE html><html><head><title>My Page</title></head><body>”);
res.write(“<div id=’content’>”);
res.write(renderToString(<MyPage/>));
res.write(“</div></body></html>”);
res.end();
});
复制代码
客户端应用 render 办法来生成 HTML

import ReactDOM from ‘react-dom’;
import MyPage from “./MyPage”;
ReactDOM.render(<MyPage />, document.getElementById(‘app’));
复制代码
react 性能检测工具
react16 版本之前,咱们能够应用 react-addons-perf 工具来查看,而在最新的 16 版本,咱们只须要在 url 后加上?react_pref。

首先来理解一下 react-addons-perf。react-addons-perf 这是 React 官网推出的一个性能工具包,能够打印出组件渲染的工夫、次数、浪费时间等。简略说几个 api,具体用法可参考官网:

Perf.start() 开始记录

Perf.stop() 完结记录

Perf.printInclusive() 查看所有设计到的组件 render

Perf.printWasted() 查看不须要的节约组件 render

再来理解一下,react16 版本的办法,在 url 后加上?react_pref,就能够在 chrome 浏览器的 performance,咱们能够查看 User Timeing 来查看组件的加载工夫。点击 record 开始记录,留神记录时长不要超过 20s,否则可能导致 chrome 挂起。

最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163 互相学习,咱们会有业余的技术答疑解惑

如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点 star: https://gitee.com/ZhongBangKe… 不胜感激!

退出移动版