当前位置:首页 > 技术知识 > 正文内容

到底是 react 性能拉胯?还是吃了机制的亏

maynowei9个月前 (09-21)技术知识108

前言

看过我之前文章的应该知道我们学校为了降低成本选择自主开发考试系统,并对小编所在的实验室委以重任,在框架的选择上选用了 react 作为项目框架,有些写了多年 react 的会说 react 性能拉胯,其实大多数场景是因为机制没理解透,吃了 react 机制的亏。

性能问题

  • 由于 useEffect 滥用,导致副作用泛滥,造成“页面闪烁、重渲染、数据竞态”问题
  • prop drilling 导致不必要的渲染
  • 没有请求缓存池,数据获取实时请求 API
  • 渲染优先级未控制,所有更新一律同步卡主线程
  • 非关键逻辑和关键逻辑放一起执行,block 住时间切片

实战示例

useEffect 滥用,数据一律实时请求 API

常见的 useEffect 中请求数据,例如:

useEffect(() => {
fetch(api).then(...)
},[])

这样子写有什么问题?

  • 直接在 Effects 中获取通常意味着不会预加载或缓存数据。 例如,如果组件卸载,然后再次挂载,则它必须再次获取数据。
  • 网速较慢时,容易造成网络瀑布
  • 页面会先渲染 undefind ,再更新
  • 请求的竞态问题(请求过快时,最终展示的是最先返回的数据)
  • 回到上一页面时重新请求数据

举个在项目中出现的例子如图:当回到上一页面时会重新请求数据


为什么一个请求会被发送两次呢?

官方解释:“当严格模式启动时,React 将在真正的 setup 函数首次运行前,运行一个开发模式下专有的额外 setup + cleanup 周期。这是一个压力测试,用于确保 cleanup 逻辑“映射”到了 setup 逻辑,并停止或撤消 setup 函数正在做的任何事情。”

回到正题,如何减少请求的重发,小编想到的是构建请求缓存池,缓存请求结果,封装合并、缓存、过期逻辑。

示例代码:

// 构建缓存池
const fetchCache = new Map();
// 封装请求逻辑
export async function fetchData(key: string, fetchFn: () => Promise<any>) {
if(fetchCache.has(key)) return fetchCache.get(key);
return fetchFn().then((data: any) => {
fetchCache.set(key, data);
return data;
});
}
// 实战示例
useEffect(() => {
// 请求试卷信息,无需过期逻辑
const res = await fetchData(`select(+${id})`, () => select(+id));
...
}, [id]);

实现后效果:


官方方案:

useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);

在开发过程中,您将在 Network (网络) 选项卡中看到两个 fetch。 这没有错。使用上述方法,第一个 Effect 将立即被清理,因此其变量副本将被设置为 .因此,即使有额外的请求,由于检查,它也不会影响状态。

调度优先级控制

当同时有多个 State 状态更新时,会造成 react 更新慢,默认的 React 更新是同步阻塞型的,举个例子用 setState 更新 5 个 state,React 会一次性打包全部执行。

如果有一些不重要的动画、计数器、输入监听,最好拆出低优先级更新。

import { startTransition, useState } from 'react';
const [inputValue, setInputValue] = useState(18);
const onChange = (newValue) => {
startTransition(() => {
setInputValue(newValue);
})
};

避免不必要的渲染

在 react 开发过程中,相信不少人会遇到这种问题“只是更新单个组件,却导致其它无关的组件也跟着重渲染”,我们应该怎样避免无关组件的重渲染呢?毕竟 react 的协调成本是很昂贵的。


由于考试的倒计时,触发的重渲染,导致下方导航栏也一起重渲染。

解决方案:使用 React.memo 包裹组件

在 React16.6 加入的一个专门用来优化 函数组件 (Functional Component)性能的方法: React.memo。React.memo() 是一个高阶函数,它与 React.PureComponent 类似。

示例代码:

import React from 'react';
export default React.memo(footerNav);

实现后效果如图:


其它方法:

  • PureComponent 它内置了对 shouldComponentUpdate 的实现:PureComponent 将会在 shouldComponentUpdate 中对组件更新前后的 props 和 state 进行浅比较,并根据浅比较的结果,决定是否需要继续更新流程。

使用: class footerNav extends React.PureComponent{}

缺点: 不管是 PureComponent 还是React.memo 都是对数据进行钱比较,如果引用类型的地址改变,内容没变,还是会被认定为改变。

  • Immutable.js 保证修改操作返回一个新引用,并且只修改需要修改的节点。Immutable 的结构不可变性&&结构共享性,能够快速进行数据的比较。

缺点:虽然 ImmutableJS 可以在某些情况解决重复渲染,但是如果需要频繁地与服务器交互,那么 Immutable 对象就需要不断地与原生 js 进行转换,操作起来显得很繁琐,并且这种方案某种层面上来说有一定心智成本。替换方案:Immer

  • reselect缓存 将输入与输出建立映射,缓存函数产出结果。只要输入一致,那么会直接吐出对应的输出结果,从而保证计算结果不变。。这种方式是通过缓存,使用 reselect 缓存函数执行结果,来避免产生新的对象。小编也没有使用过,有兴趣的可以试试。

渲染阻塞识别与组件分段懒加载

一个页面白屏时间长,可能由某个组件加载速度引起的,例如:图表组件、长列表的数据加载等等

解决方案:用 lazy + Suspense 做模块切

const ExamPage = lazy(() => import('@/pages/examPage')); <Suspense fallback={<div>loading...</div>}> <ExamPage data={data} /> </Suspense>

总结

本文主要介绍了一些利用 react 机制优化项目的技巧,小编对 react 的学习也不是很深,如果有什么更好的方案可以在评论区指点一下。

相关文章

IT博物馆之Objective-C诞生(micro博物馆)

1984年,Objective-C诞生。设计者:布莱德·考克斯(Brad Cox)、汤姆·洛夫(Tom Love)Objective-C是面向对象的通用、高级编程语言。它扩展了标准的 ANSI C,将...

Android监听滚动视图(监听页面滚动)

Android UI Libs之Android-ObservableScrollView1. 说明Android-ObservableScrollView,顾名思义,Android上观察滚动的视图,可...

C++11 同步机制:互斥锁和条件变量

前段时间,我研究了 ROS2(Jazzy)机器人开发系统,并将官网中比较重要的教程和概念,按照自己的学习顺序翻译成了中文,进行了整理和记录。到目前为止,已经整理了20多篇文章。如果你想回顾之前的内容,...

如何在Go中同步线程(go语言同步锁)

单线程代码已经带来头痛。添加第二个线程,就是从基础头痛升级了。解决方案?互斥锁:线程和数据的交通警察。一旦你理解了它们,线程同步就变成了第二本能,语言无关。在C++和Go中工作,我遇到过所有常见的混乱...

ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务

早上同事用PL/SQL连接虚拟机中的Oracle数据库,发现又报了“ORA-12514 TNS 监听程序当前无法识别连接描述符中请求服务”错误,帮其解决后,发现很多人遇到过这样的问题,因此写着这里。也...

每天学Java!Java Bean是什么概念(java bean有什么用)

对于初学Java,或者是刚接触J2EE的人来说,Java bean确实是一个不太好理解的概念,对于一些专业的解释呢,好像看起来也不是那么容易理解。所以小华君今天就准备跟大家说一说Java bean的概...