react 性能优化
shouldComponentUpdate
对于对视图没有关联,但可能更新的数据,可以用
shouldComponentUpdate
过滤掉,减少无所谓的更新
返回一个 Boolean
值,true
则更新,false
则不更新,默认为true
shouldComponentUpdate(nextProps,nextState){if(this.props.xxx !== nextProps.xxx){return true;}else {return false;}
}
PureComponent
对组件的
state
和props
仅浅对比更新,即传入的是对象,单纯的更改对象属性是无法触发更新,需要更改对象的引用(对于非基本类型的对比,对比的是引用的地址块)
可以配合immutable.js
使用,以达到对象更改属性时更新的效果。
Redux 配合 immutable.js 使用
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构)。
Immutable.js 了解一下?– 掘金
memo
React.memo
为高阶组件。它与React.PureComponent
非常相似,但它适用于函数组件,但 不适用于class
组件 。
如果你的函数组件在给定相同props
的情况下渲染相同的结果,那么你可以通过将其包装在React.memo
中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
原理同 PureComponent
,但仅对props
仅做浅对比。
如果要手动添加对比过程,可以在第二个参数传入自定义的对比函数。
function MyComponent(props) {/* 使用 props 渲染 */}
function areEqual(prevProps, nextProps) {
/* 如果函数返回 true,就会跳过更新。*/
if(nextProps.xxx !== prevProps.xxx){return false;}else {return true;}
}
export default React.memo(MyComponent, areEqual);
useMemo
useMemo
允许你通过「记住」上一次计算结果的方式在多次渲染的之间缓存计算结果。
与 memo
的区别是 useMemo
作用于计算以及组件。
- 第一个参数是主体内容
- 第二个参数是数组,传入要记住的参数
作用与function
:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
作用于组件:
function Parent({a, b}) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
注意这种方式在循环中是无效的,因为
Hook
调用 不能 被放在循环中。但你可以为列表项抽取一个单独的组件,并在其中调用useMemo
。
useCallback
useCallback
和useMemo
有些相似。它接收一个内联函数和一个数组,它返回的是一个记忆化版本的函数。
useCallback(fn, deps)
相当于 useMemo(() => fn, deps)
。
lazy & Suspense
React 集成了
lazy
和Suspense
。lazy
会进行代码分割,它能让你像渲染常规组件一样处理动态引入(的组件)。。fallback
属性接受任何在组件加载过程中你想展示的React
元素。你可以将Suspense
组件置于懒加载组件之上的任何位置。你甚至可以用一个Suspense
组件包裹多个懒加载组件。
???? 对路由组件进行代码分割、懒加载。
import React, {lazy, Suspense} from "react";
import {renderRoutes} from "react-router-config";
import {HashRouter, Redirect} from "react-router-dom";
const Page1Comp = lazy(() => import("../Page1"));
const Page1 = props => {
return (<Suspense fallback={<p>loading</p>}>
<Page1Comp {...props} />
</Suspense>
);
};
const Page2Comp = lazy(() => import("../Page2"));
const Page2 = props => {
return (<Suspense fallback={<p>loading</p>}>
<Page2Comp {...props} />
</Suspense>
);
};
const routes = [{ path: "/", exact: true, render: () => <Redirect to={"/page1"} /> },
{path: "/page1", component: Page1},
{path: "/page2", component: Page2}
];
function App() {
return (
<HashRouter>
<div className="App">
<h1>Hello</h1>
{renderRoutes(routes)}
</div>
</HashRouter>
);
}
export default App;
Fragment
在 jsx 里面,会有很多无意义但必须有的的嵌套。可以使用 Fragment 代替减少无谓的嵌套。
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
// or
render() {
return (
<>
<ChildA />
<ChildB />
<ChildC />
</>
);
}
不适用内联函数
<input type="button" onClick={(e) => {this.setState({inputValue: e.target.value}) }} />
???? 上面的函数创建了内联函数。每次调用 render 函数时都会创建一个函数的新实例,render 函数会将该函数的新实例绑定到该按钮。
改成如下????:
export default class InlineFunctionComponent extends Component {setNewStateData = (event) => {
this.setState({inputValue: e.target.value})
}
render() {return <input type="button" onClick={this.setNewStateData} />
}
}
函数绑定在 constructor 完成
<input type="button" value="Click" onClick={this.handleButtonClick.bind(this)} />
每次调用 render 函数时都会创建并使用绑定到当前上下文的新函数,但在每次渲染时使用已存在的函数效率更高。优化方案如下:
constructor() {this.handleButtonClick = this.handleButtonClick.bind(this)
}
箭头函数与 constructor 函数绑定
当我们添加箭头函数时,该函数被添加为对象实例,而不是类的原型属性。这意味着如果我们多次复用组件,那么在组件外创建的每个对象中都会有这些函数的多个实例。
每个组件都会有这些函数的一份实例,影响了可复用性。此外因为它是对象属性而不是原型属性,所以这些函数在继承链中不可用。
因此箭头函数确实有其缺点。实现这些函数的最佳方法是在构造函数中绑定函数。
节流和防抖
节流函数
节流是将多次执行变成每隔一段时间执行。
/**
* underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {object} options 如果想忽略开始函数的的调用,传入{leading: false}。* 如果想忽略结尾函数的调用,传入{trailing: false}
* 两者不能共存,否则函数不能执行
* @return {function} 返回客户调用函数
*/
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null;
// 之前的时间戳
var previous = 0;
// 如果 options 没传则设为空对象
if (!options) options = {};
// 定时器回调函数
var later = function() {
// 如果设置了 leading,就将 previous 设为 0
// 用于下面函数的第一个 if 判断
previous = options.leading === false ? 0 : _.now();
// 置空一是为了防止内存泄漏,二是为了下面的定时器判断
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function() {
// 获得当前时间戳
var now = _.now();
// 首次进入前者肯定为 true
// 如果需要第一次不执行函数
// 就将上次时间戳设为当前的
// 这样在接下来计算 remaining 的值时会大于 0
if (!previous && options.leading === false) previous = now;
// 计算剩余时间
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 如果当前调用已经大于上次调用时间 + wait
// 或者用户手动调了时间
// 如果设置了 trailing,只会进入这个条件
// 如果没有设置 leading,那么第一次会进入这个条件
// 还有一点,你可能会觉得开启了定时器那么应该不会进入这个 if 条件了
// 其实还是会进入的,因为定时器的延时
// 并不是准确的时间,很可能你设置了 2 秒
// 但是他需要 2.2 秒才触发,这时候就会进入这个条件
if (remaining <= 0 || remaining > wait) {
// 如果存在定时器就清理掉否则会调用二次回调
if (timeout) {clearTimeout(timeout);
timeout = null;
}
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
// 判断是否设置了定时器和 trailing
// 没有的话就开启一个定时器
// 并且不能不能同时设置 leading 和 trailing
timeout = setTimeout(later, remaining);
}
return result;
};
};
防抖
防抖动是将多次执行变为最后一次执行。
/**
* 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行
*
* @param {function} func 回调函数
* @param {number} wait 表示时间窗口的间隔
* @param {boolean} immediate 设置为 ture 时,是否立即调用函数
* @return {function} 返回客户调用函数
*/
export function debounce(func, wait = 200, immediate = false) {
let timer, context, args
// 延迟执行函数
const later = () => setTimeout(() => {
// 延迟函数执行完毕,清空缓存的定时器序号
timer = null
// 延迟执行的情况下,函数会在延迟函数中执行
// 使用到之前缓存的参数和上下文
if (!immediate) {func.apply(context, args)
context = args = null
}
}, wait)
// 这里返回的函数是每次实际调用的函数
return function (...params) {
// 如果没有创建延迟执行函数(later),就创建一个
if (!timer) {timer = later()
// 如果是立即执行,调用函数
// 否则缓存参数和调用上下文
if (immediate) {func.apply(this, params)
} else {
context = this
args = params
}
// 如果已有延迟执行函数(later),调用的时候清除原来的并重新设定一个
// 这样做延迟函数会重新计时
} else {clearTimeout(timer)
timer = later()
context = this
args = params
}
}
}
避免内联样式
用 CSS 动画代替 JavaScript 动画
服务器 gzip 压缩
Web Workers 处理 CPU 密集任务
整理学习自你需要掌握的 21 个 React 性能优化技巧