您现在的位置是:网站首页> 编程资料编程资料
React 性能优化方法总结_React_
2023-05-24
381人已围观
简介 React 性能优化方法总结_React_
前言
要讲清楚性能优化的原理,就需要知道它的前世今生,需要回答如下的问题:
- React 是如何进行页面渲染的?
- 造成页面的卡顿的罪魁祸首是什么呢?
- 我们为什么需要性能优化?
- React 有哪些场景会需要性能优化?
- React 本身的性能优化手段?
- 还有哪些工具可以提升性能呢?
为什么页面会出现卡顿的现象?
为什么浏览器会出现页面卡顿的问题?是不是浏览器不够先进?这都 2202 年了,怎么还会有这种问题呢?
实际上问题的根源来源于浏览器的刷新机制。
我们人类眼睛的刷新率是 60Hz,浏览器依据人眼的刷新率 计算出了
1000 Ms / 60 = 16.6ms
也就是说,浏览器要在16.6Ms 进行一次刷新,人眼就不会感觉到卡顿,而如果超过这个时间进行刷新,就会感觉到卡顿。
而浏览器的主进程在仅仅需要页面的渲染,还需要做解析执行Js,他们运行在一个进程中。
如果js的在执行的长时间占用主进程的资源,就会导致没有资源进行页面的渲染刷新,进而导致页面的卡顿。
那么这个又和 React 的性能优化又有什么关系呢?
React 到底是在哪里出现了卡顿?
基于我们上的知识,js 长期霸占浏览器主线程造成无法刷新而造成卡顿。
那么 React 的卡顿也是基于这个原因。
React 在render的时候,会根据现有render产生的新的jsx的数据和现有fiberRoot 进行比对,找到不同的地方,然后生成新的workInProgress,进而在挂载阶段把新的workInProgress交给服务器渲染。
在这个过程中,React 为了让底层机制更高效快速,进行了大量的优化处理,如设立任务优先级、异步调度、diff算法、时间分片等。
整个链路就是了高效快速的完成从数据更新到页面渲染的整体流程。
为了不让递归遍历寻找所有更新节点太大而占用浏览器资源,React 升级了fiber架构,时间分片,让其可以增量更新。
为了找出所有的更新节点,设立了diff算法,高效的查找所有的节点。
为了更高效的更新,及时响应用户的操作,设计任务调度优先级。
而我们的性能优化就是为了不给 React 拖后腿,让其更快,更高效的遍历。
那么性能优化的奥义是什么呢??
就是控制刷新渲染的波及范围,我们只让改更新的更新,不该更新的不要更新,让我们的更新链路尽可能的短的走完,那么页面当然就会及时刷新不会卡顿了。
React 有哪些场景会需要性能优化?
- 父组件刷新,而不波及子组件
- 组件自己控制自己是否刷新
- 减少波及范围,无关刷新数据不存入state中
- 合并 state,减少重复 setState 的操作
- 如何更快的完成diff的比较,加快进程
我们分别从这些场景说一下:
一:父组件刷新,而不波及子组件。
我们知道 React 在组件刷新判定的时候,如果触发刷新,那么它会深度遍历所有子组件,查找所有更新的节点,依据新的jsx数据和旧的 fiber ,生成新的workInProgress,进而进行页面渲染。
所以父组件刷新的话,子组件必然会跟着刷新,但是假如这次的刷新,和我们子组件没有关系呢?怎么减少这种波及呢?
如下面这样:
export default function Father1 (){ let [name,setName] = React.useState(''); return ( {name} ) } function Children(){ return ( 这里是子组件 ) }运行结果:

可以看到我们的子组件被波及了,解决办法有很多,总体来说分为两种:
- 子组件自己判断是否需要更新 ,典型的就是 PureComponent,shouldComponentUpdate,memo
- 父组件对子组件做个缓冲判断
第一种:使用 PureComponent
使用 PureComponent 的原理就是它会对state 和props进行浅比较,如果发现并不相同就会更新。
export default function Father1 (){ let [name,setName] = React.useState(''); return ( {name} ) } class Children extends React.PureComponent{ render() { return ( 这里是子组件 ) } }执行结果:

实际上PureComponent就是在内部更新的时候调用了会调用如下方法来判断 新旧state和props
function shallowEqual(objA: mixed, objB: mixed): boolean { if (is(objA, objB)) { return true; } if ( typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null ) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (let i = 0; i < keysA.length; i++) { const currentKey = keysA[i]; if ( !hasOwnProperty.call(objB, currentKey) || !is(objA[currentKey], objB[currentKey]) ) { return false; } } return true; }它的判断步骤如下:
- 第一步,首先会直接比较新老
props或者新老state是否相等。如果相等那么不更新组件。 - 第二步,判断新老
state或者props,有不是对象或者为null的,那么直接返回 false ,更新组件。 - 第三步,通过
Object.keys将新老props或者新老state的属性名key变成数组,判断数组的长度是否相等,如果不相等,证明有属性增加或者减少,那么更新组件。 - 第四步,遍历老
props或者老state,判断对应的新props或新state,有没有与之对应并且相等的(这个相等是浅比较),如果有一个不对应或者不相等,那么直接返回false,更新组件。 到此为止,浅比较流程结束,PureComponent就是这么做渲染节流优化的。
在使用PureComponent时需要注意的细节;
由于PureComponent 使用的是浅比较判断state和props,所以如果我们在父子组件中,子组件使用PureComponent,在父组件刷新的过程中不小心把传给子组件的回调函数变了,就会造成子组件的误触发,这个时候PureComponent就失效了。
细节一:函数组件中,匿名函数,箭头函数和普通函数都会重新声明
下面这些情况都会造成函数的重新声明:
箭头函数
setValue(value)}/>
匿名函数
普通函数
export default function Father1 (){ let [name,setName] = React.useState(''); let [value,setValue] = React.useState('') const setData=(value)=>{ setValue(value) } return ( {name} ) } class Children1 extends React.PureComponent{ render() { return ( 这里是子组件 ) } }执行结果:

可以看到子组件的 PureComponent 完全失效了。这个时候就可以使用useMemo或者 useCallback 出马了,利用他们缓冲一份函数,保证不会出现重复声明就可以了。
export default function Father1 (){ let [name,setName] = React.useState(''); let [value,setValue] = React.useState('') const setData= React.useCallback((value)=>{ setValue(value) },[]) return ( {name} ) }看结果:

可以看到我们的子组件这次并没有参与父组件的刷新,在React Profiler中也提示,Children1并没有渲染。
细节二:class组件中不使用箭头函数,匿名函数
原理和函数组件中的一样,class 组件中每一次刷新都会重复调用render函数,那么render函数中使用的匿名函数,箭头函数就会造成重复刷新的问题。
export default class Father extends React.PureComponent{ constructor(props) { super(props); this.state = { name:"", count:"", } } render() { return ( {this.state.name} this.setState({count:11})}/> ) } }执行结果:

而优化这个非常简单,只需要把函数换成普通函数就可以。
export default class Father extends React.PureComponent{ constructor(props) { super(props); this.state = { name:"", count:"", } } setCount=(count)=>{ this.setState({count}) } render() { return ( {this.state.name} ) } }执行结果:

细节三:在 class 组件的render函数中bind 函数
这个细节是我们在class组件中,没有在constructor中进行bind的操作,而是在render函数中,那么由于bind函数的特性,它的每一次调用都会返回一个新的函数,所以同样会造成PureComponent的失效
export default class Father extends React.PureComponent{ //... setCount(count){ this.setCount({count}) } render() { return ( {this.state.name} ) } }看执行结果:

优化的方式也很简单,把bind操作放在constructor中就可以了。
constructor(props) { super(props); this.state = { name:"", count:"", } this.setCount= this.setCount.bind(this); }执行结果就不在此展示了。
第二种:shouldComponentUpdate
class 组件中 使用 shouldComponentUpdate 是主要的优化方式,它不仅仅可以判断来自父组件的
nextprops,还可以根据
相关内容
- 一文详解node.js有哪些全局对象呢_node.js_
- Node 文件查找优先级及 Require 方法文件查找策略_node.js_
- 如何处理elementUI中表格多选框禁用的问题_vue.js_
- vue3的ref、isRef、toRef、toRefs、toRaw详细介绍_vue.js_
- Vue路由配置方法详细介绍_vue.js_
- Vue首页界面加载优化实现方法详解_vue.js_
- Vue tagsview实现多页签导航功能流程详解_vue.js_
- js如何读取csv内容拼接成json_javascript技巧_
- React路由参数传递与嵌套路由的实现详细讲解_React_
- React通过classnames库添加类的方法_React_
