React性能优化的实现方法详解

目录
  • 前言
  • 遍历视图key使用
  • React.memo缓存组件
  • React.useCallback让函数保持相同的引用
  • 避免使用内联对象
  • 使用React.useMemo缓存计算结果或者组件
  • 使用React.Fragment片段
  • 组件懒加载
  • 通过 CSS 加载和卸载组件
  • 变与不变的地方做分离
  • 总结

前言

想要写出高质量的代码,仅仅靠框架底层帮我们的优化还远远不够,在编写的过程中,需要我们自己去使用提高的 api,或者根据它底层的原理去做一些优化,以及规范。

相比于 Vue ,React 不会再框架源码层面帮助我们直接解决一下基本的性能优化相关,而是提供一下 API (Hooks)让我们自己去优化我们的应用,也是它自身更灵活的一种原因之一。

下面总结了一些从编写 React 代码层面上能做的优化点。

遍历视图key使用

key 的作用能够帮助我们识别哪些元素改变了,比如添加和删除。在 React 更新时,会触发 React Diff 算法,diff 过程中过借助 key 值来判断元素是新创建还是需要移动的元素。React 会保存这个辅助状态。从而减少不必要的元素渲染。

key 的值最好是当前列表中拥有独一无二的字符串。开发中通常用 id 等这些作为元素的 key 值。

当前的列表不会发生操作,万不得已 可以使用 index 作为 key 值。

key 应该具有稳定,可预测,以及列表内唯一的特质。不稳定的 key 比如 Math.random() 生成的会 导致很多组件实例和 DOM 节点被不必要的重新创建,这可能导致性能下降和子组件状态丢失等等。

React.memo缓存组件

react 是单向数据流,父组件状态的更新也会让子组件一起重新渲染更新,即使子组件的状态没有发生变化,不会像 Vue 一样能够具体监听到某一个组件状态的变化然后更新当前的这个组件。

因此可以用 React.memo 缓存组件,这样只有传入当前组件状态值变化时才会重新渲染,值相同那么就会缓存组件。

// 子组件
const Child = React.memo(() => {
  console.log("child");
  return (
    <div>
      Child
    </div>
  );
});
// 父组件
function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <h3>{count}</h3>
      <button onClick={() => setCount(count + 1)}>Count++ </button>
      <Child />
    </div>
  );
}

上面代码 <Child /> 组件添加上 memo 每次点击 count ++ 那么就会不会重新渲染了。

React.useCallback让函数保持相同的引用

像上面的例子,如果父组件想拿到子组件的状态值,通常会使用 callback 的方式传递出去给父组件。

interface ChildProps {
  onChangeNum: (value: number) => void;
}
const Child: React.FC<ChildProps> = React.memo(({ onChangeNum }) => {
  console.log("child");
  const [num, setNum] = useState(0);
  useEffect(() => {
    onChangeNum(num);
  }, [num]);
  return (
    <div>
      <button
        onClick={() => {
          setNum((prevState) => {
            return prevState + 1;
          });
        }}
      >
        Child
      </button>
    </div>
  );
});
function App() {
  const [count, setCount] = useState(0);
  return (
    <div className="App">
      <h3>{count}</h3>
      <button onClick={() => setCount(count + 1)}>Count++ </button>
      <Child
        onChangeNum={(num) => {
          console.log(num, "childNum");
        }}
      />
    </div>
  );
}

组件每次更新 num 值,父组件通过 onChangeNum 回掉函数方式接受。

注意刚才说的 memo 能够在组件传入值不变的情况下缓存组件避免重新渲染,但是,这里又失效了。这是为什么呢?

原因就是父组件更新了,每次都会创建一个新的 onChangeNum ,相当于属于不同的引用了,在每次 props 传递的回掉函数都不相同,所以 memo 失去了作用。

那么该怎么解决?那就是使用 useCallback hook 帮助我们保持相同的引用。

<Child
  onChangeNum={useCallback((num) => {
    console.log(num, "childNum");
  }, [])}
/>

开发中使用了 memo 缓存了组件,还需要注意是否有匿名函数传递给子组件。

并不一定只在这种情况下才使用 useCallback ,比如一个请求函数或者逻辑处理函数,也可以用 useCallback 包裹,不过要注意,内部引用了外部的状态或者值的相关联,那么需要在第二个参数也就是依赖数组里面添加上用到的某些值。

避免使用内联对象

在使用内联对象,react 每次重新渲染时会重新创建此对象,在更新组件对比 props ,oldProps === newProps 只要为 false 那么就会 re-render

如果TestComponent 组件重新渲染,那么就会新建创建 someProps 引用。传递给 RootComponent 组件每次判断新旧 props 结果不同,导致也重新渲染。

const TestComponent = () => {
  const someProps = { value: '1' }
  return <RootComponent someProps={someProps} />;
};

更好的方式是,使用 ES6 扩展运算符的将这个对象展开,引用类型变为值类型传递,这样再对比 props 就会相等了。

const TestComponent = () => {
  const someProps = { value: '1' }
  return <RootComponent {...someProps} />;
};

使用React.useMemo缓存计算结果或者组件

如 React 文档所说,useMemo 的基本作用是,避免每次渲染都进行高开销的计算。

如果是一个功能组件里面,涉及到大型的计算,组件每次重新渲染导致都从新调用大型的计算函数,这是非常消耗性能的,我们可以使用 useMemo 来缓存这个函数的计算结果,来减少 JavaScript 在呈现组件期间必须执行的工作量,来缩短阻塞主线程的时间。

// 只有当 id 发生变化的时候才会从新计算
const TestComponent = () => {
  const value = useMemo(() => {
    return expensiveCalculation()
  }, [id])
  return <Component countValue={value} />
}

在使用 useMemo 缓存计算结果之前,还需要在适当的地方应用,useMemo 也是有成本的,它也会增加整体程序初始化的耗时,除非这个计算真的很昂贵,比如阶乘计算。

所以并不适合全局使用,它更适合做局部的优化。不应该过度 useMemo。

另外在缓存结果值的同时,还可以用来缓存组件。

比如有一个全局 context ,随着长期项目迭代 context 里面塞了很多状态,我们知道,context 的 value 发生变化,就会导致组件的重新渲染,而这个组件时一个很消耗性能的大型组件,只会被其中一个变量所影响才重新渲染,这时候就可以考虑使用 useMemo 进行缓存。

const TestComponent = () => {
  const appContextValue = useContext(AppContext);
  const theme = appContextValue.theme;
  return useMemo(() => {
    return <RootComponent className={theme} />;
  }, [theme]);
};

<RootComponent /> 只有在 theme 变量发生变化的时候重新渲染。

使用React.Fragment片段

react 有规定组件中必须有一个父元素,但是在某些情况下,根标签不需要任何的属性,这会导致整个应用程序内创建许多无用的元素,那么这个标签的作用并没有太大的意义。

const TestComponent = () => {
  return (
    <div>
      <ChildA />
      <ChildB />
      <ChildC />
    </div>
  );
}

实际上页面上的元素越多,DOM结构嵌套越深,加载所需的时间就越多,也会增加浏览器的渲染压力。

因此 React 提供了 Fragment 组件来代替包裹外层,它不会帮我们额外的创建外层 div 标签。

const TestComponent = () => {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

或者另一种简洁的方式使用空标签 <></> 代替也是一样的效果:

const TestComponent = () => {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  );
}

另外还有一些实用的场景,根据条件渲染元素

const TestComponent = () => {
  const { isLogin, name } = useApp();
  return (
    <>
      {isLogin ? (
        <>
          <h3>Welcome {name}</h3>
          <p>You are logged in!</p>
        </>
      ) : (
        <h3>go login...</h3>
      )}
    </>
  );
};

组件懒加载

应用程序初始化加载的快慢也跟组件的数量有关,因此在初始化的时候,一些我们看不见的页面,也就是最开始用不到的组件可以选择延迟加载组件,我们可以想到的是路由的懒加载,这样来提升页面的加载速度和响应时间。

react 提供了 React.LazyReact.Suspense 来帮我们实现组件的懒加载。

import React, { lazy, Suspense } from 'react';
const AvatarComponent = lazy(() => import('./AvatarComponent'));
const renderLoader = () => <p>Loading</p>;
const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspense 作用就是弥补在 Lazy 组件加载完成之前这段空白时间所能做的事情,尤其在组件较大,或者在较弱的设备和网络中,就可以通过 fallback 属性添加一个 loading 提示用户正在加载的状态。异步组件加载完成之后就会显示出来。

如果单独使用 lazy React 会在控制台发出错误提示!

通过 CSS 加载和卸载组件

渲染是昂贵的,如果频繁加载/卸载‘很重’的组件,这个操作可能非常消耗性能或者导致延迟。正常情况下,我们都会用三元运算符在判断加载显示,也导致了一个问题,每次频繁更新,触发加载不同的组件,就会有一定的性能损耗。这时我们可以使用 CSS 属性将其隐藏,让 DOM 能够保留在页面当重。

**不过这种方式并不是万能的,可能会导致一些布局或者窗口发生错位的问题。**但我们应该选择在不是这种情况下使用调整CSS的方法。另外一点,将不透明度调整为0对浏览器的成本消耗几乎为0(因为它不会导致重排),并且应尽可能优先于更该visibility 和 display。

// 避免对大型的组件频繁对加载和卸载
const ViewExample = () => {
  const [isTest, setIsTest] = useState(true)
  return (
    <>
      { isTest ? <ViewComponent /> : <TestComponent />}
    </>
  );
};
// 使用该方式提升性能和速度
const visibleStyles = { opacity: 1 };
const hiddenStyles = { opacity: 0 };
const ViewExample = () => {
  const [isTest, setIsTest] = useState(true)
  return (
    <>
      <ViewComponent style={!isTest ? visibleStyles : hiddenStyles} />
			<TestComponent style={{ isTest ? visibleStyles : hiddenStyles }} />
    </>
  );
};

变与不变的地方做分离

通常使用 useMemo、useCallback 进行优化,这里说说不借助这些Hooks进行优化,

变与不变做分离的概念来源,其实就是因为自身的react 的机制,父组件的状态更新了,所有的子组件得跟着一起渲染,意思是将有状态的组件和无状态的组件分离开。

function ExpensiveCpn() {
  console.log("ExpensiveCpn");
  let now = performance.now();
  while (performance.now() - now < 100) {}
  return <p>耗时的组件</p>;
}
export default function App() {
  const [num, updateNum] = useState("");
  return (
    <>
      <input
        type="text"
        onChange={(e) => updateNum(e.target.value)}
        value={num}
      />
      <ExpensiveCpn />
    </>
  );
}

上面输入框输入都会刷新组件<ExpensiveCpn/>,我们可以不使用 useMemo 等API就能控制渲染其实就是将变得和不变的分离开

(0)

相关推荐

  • React 性能优化方法总结

    目录 前言 为什么页面会出现卡顿的现象? React 到底是在哪里出现了卡顿? React 有哪些场景会需要性能优化? 一:父组件刷新,而不波及子组件. 第一种:使用 PureComponent 第三种:函数组件如何判断props的变化的更新呢? 使用 React.memo函数 使用 React.useMemo来实现对子组件的缓冲 一:组件自己控制自己是否刷新 三:减少波及范围,无关刷新数据不存入state中 场景一:无意义重复调用setState,合并相关的state 场景二:和页面刷新没有相

  • 浅谈React组件之性能优化

    高德纳: "我们应该忘记忽略很小的性能优化,可以说97%的情况下,过早的优化是万恶之源,而我们应该关心对性能影响最关键的另外3%的代码." 不要将性能优化的精力浪费在对整体性能提高不大的代码上,而对性能有关键影响的部分,优化并不嫌早.因为,对性能影响最关键的部分,往往涉及解决方案核心,决定整体的架构,将来要改变的时候牵扯更大. 1. 单个React组件的性能优化 React利用Virtual DOM来提升渲染性能,虽然每一次页面更新都是最组件的从新渲染,但是并不是将之前的渲染内容全部抛

  • React 性能优化之非必要的渲染问题解决

    目录 1. 非必要组件渲染 2. 解决方案之 shouldComponentUpdate 3. 解决方案之 PureComponent 4. 解决方案之 React.memo 5. useMemo 和 useCallback 1. 非必要组件渲染 在 React 工程中,在改变 React 状态时,我们希望对整个页面的影响越小越好.然而实际情况是更改掉某些属性之后,除了会导致组件本身的重新渲染,也可能会导致其相关的组件也发生重新渲染.请看下面的例子: 新建一对父子组件 // 父组件: impor

  • React函数式组件的性能优化思路详解

    优化思路 主要优化的方向有2个: 减少重新 render 的次数.因为在 React 里最重(花时间最长)的一块就是 reconction(简单的可以理解为 diff),如果不 render,就不会 reconction. 减少计算的量.主要是减少重复计算,对于函数式组件来说,每次 render 都会重新从头开始执行函数调用. 在使用类组件的时候,使用的 React 优化 API 主要是:shouldComponentUpdate和 PureComponent 那么在函数式组件中,我们怎么做性能

  • React 组件性能最佳优化实践分享

    目录 React 组件性能优化最佳实践 组件卸载前进行清理操作 类组件使用纯组件PureComponent 什么是纯组件 什么是浅层比较 shouldComponentUpdate 纯函数组件使用React.memo优化性能 memo 基本使用 memo 传递比较逻辑 使用组件懒加载 路由组件懒加载 根据条件进行组件懒加载(适用于组件不会随条件频繁切换) 使用Fragment 避免额外标记 不要使用内联函数定义 在构造函数中进行函数this绑定 类组件中的箭头函数 优化条件渲染 避免使用内联样式

  • React 首页加载慢问题性能优化案例详解

    学习了一段时间React,想真实的实践一下.于是便把我的个人博客网站进行了重构.花了大概一周多时间,网站倒是重构的比较成功,但是一上线啊,那个访问速度啊,是真心慢,慢到自己都不能忍受,那么小一个网站,没几篇文章,慢成那样,不能接受.我不是一个追求完美的人,但这样可不行.后面大概花了一点时间进行性能的研究.才发现慢是有原因的. React这类框架? 目前主流的前端框架React.Vue.Angular都是采用客户端渲染(服务端渲染暂时不在本文的考虑范围内).这当然极大的减轻了服务器的压力.相对的浏

  • React性能优化的实现方法详解

    目录 前言 遍历视图key使用 React.memo缓存组件 React.useCallback让函数保持相同的引用 避免使用内联对象 使用React.useMemo缓存计算结果或者组件 使用React.Fragment片段 组件懒加载 通过 CSS 加载和卸载组件 变与不变的地方做分离 总结 前言 想要写出高质量的代码,仅仅靠框架底层帮我们的优化还远远不够,在编写的过程中,需要我们自己去使用提高的 api,或者根据它底层的原理去做一些优化,以及规范. 相比于 Vue ,React 不会再框架源

  • Android性能优化大图治理示例详解

    目录 引言 1 自定义大图View 1.1 准备工作 1.2 图片宽高适配 1.3 BitmapRegionDecoder 2 大图View的手势事件处理 2.1 GestureDetector 2.2 双击放大效果处理 2.3 手指放大效果处理 引言 在实际的Android项目开发中,图片是必不可少的元素,几乎所有的界面都是由图片构成的:像列表页.查看大图页等,都是需要展示图片,而且这两者是有共同点的,列表展示的Item数量多,如果全部加载进来势必会造成OOM,因此列表页通常采用分页加载,加上

  • React使用refs操作DOM方法详解

    在react框架 甚至说是三大框架中都是不太支持大家直接去操作dom的 因为也没什么必要 当然也会有特殊情况 例如视频播放 强制动画 第三方插件的一些渲染或初始化 官方也给了我们对应的解决办法 那就是refs 我们来简单写一个 我们先在constructor中定义一个虚拟dom的控制 参考代码如下 constructor(props){ super(props); this.divDaimin = React.createRef() this.state = { } } 这里 我们就通过Reac

  • React文件分段上传实现方法详解

    目录 原理 方案 antd Upload 文件分片 MD5 发送分片请求 显示上传进度 最近做了大文件(文件夹)分片上传的需求,记录一下. 原理 前端进行大文件分片上传的方案几乎都是利用Blob.prototype.slice方法对文件进行分片,用数组将每一个分片存起来,最后将分片发给后端.由于并发的原因,需要给每个分片给定index,方便后端进行拼接. 方案 我在做需求之前看了网上的一些方案,大多数是前端进行分片.发送分片,在发送完所有分片请求之后,再给后端发送一个合并文件的请求.但其实也可以

  • Android性能优化死锁监控知识点详解

    目录 前言 死锁检测 线程Block状态 获取当前线程所请求的锁 通过锁获取当前持有的线程 线程启动 nativePeer 与 native Thread tid 与java Thread tid dlsym与调用 系统限制 死锁检测所有代码 总结 前言 “死锁”,这个从接触程序开发的时候就会经常听到的词,它其实也可以被称为一种“艺术”,即互斥资源访问循环的艺术,在Android中,如果主线程产生死锁,那么通常会以ANR结束app的生命周期,如果是两个子线程的死锁,那么就会白白浪费cpu的调度资

  • React中Ref 的使用方法详解

    本文实例讲述了React中Ref 的使用方法.分享给大家供大家参考,具体如下: React中Ref 的使用 React v16.6.3 在典型的React数据流中,props是父组件与其子组件交互的唯一方式.要修改子项,请使用new props 重新呈现它.但是,在某些情况下,需要在典型数据流之外强制修改子项.要修改的子项可以是React组件的实例,也可以是DOM元素.对于这两种情况,React都提供了api. 何时使用refs refs有一些很好的用例: 1.文本选择或媒体播放. 2.触发势在

  • react项目优化配置的操作详解

    目录 优化-配置CDN 优化-路由懒加载 使用步骤 代码实现 查看效果 优化-配置CDN 通过 craco 来修改 webpack 配置,从而实现 CDN 优化 yarn add @craco/craco //或者 npm install @craco/craco --save 在项目 根目录新建craco.config.js文件 // 添加自定义对于webpack的配置 const path = require('path') const { whenProd, getPlugin, plug

  • 实现React单页应用的方法详解

    首先在学习这门框架前,你需要对以下知识有所了解: 1.原生JS基础 2.CSS基础 3.npm包管理基础 4.webpack构建项目基础      5.ES6规范 以上五个知识点也是目前学习其他前端框架所必须了解的前置任务. JS和CSS就不多说了,npm是目前最提倡也是占据主导地位的包管理工具,还在用bower或者其他工具的童鞋可以考虑下了.而webpack作为新一代打包工具,已经在前端打包工具中独占鳌头,和Browserify相比也有很大优势.至于ES6规范虽然现在主流浏览器还不兼容,但可以

  • tsc性能优化Project References使用详解

    目录 什么是 Project References 示例项目结构 不使用 Project References 带来的问题 tsconfig.json 的 references 配置项 tsconfig.json 的 composite 配置项 使用 Project References 改造示例项目 全量构建 增量构建 对__test__测试代码的处理 总结 什么是 Project References 在了解一个东西是什么的时候,直接看其官方定义是最直观的 TypeScript: Docum

  • Mysql巧用join优化sql的方法详解

    0. 准备相关表来进行接下来的测试 相关建表语句请看:https://github.com/YangBaohust/my_sql user1表,取经组 +----+-----------+-----------------+---------------------------------+ | id | user_name | comment | mobile | +----+-----------+-----------------+-----------------------------

随机推荐