React性能优化

三个可以优化的地方

避免过度多次渲染 

组件会在以下情况下重新渲染

注意:例如组件组合的形式,<Test><Counter></Counter></Test>,即使Test发生了重新渲染,Counter也不会重新渲染。另外使用React这样的库或框架时,渲染指的是组件函数被调用的过程。这个过程并不一定意味着文档对象模型(DOM)实际上被更新了。它只是意味着组件的逻辑被执行,可能会重新计算组件的状态和属性。

 什么是过度渲染?

  • 无效渲染是指组件函数被调用了,但是并没有导致DOM的任何变化。
  • 这通常发生在组件的状态或属性没有改变,但组件仍然被重新渲染的情况下。这种渲染是不必要的,因为它消耗了资源但没有带来任何视觉上的变化。
  • 当然这在react中大部分情况下是没问题的,只有这种情况过度频繁时才会产生问题。

记忆化

组件、对象、函数都可以记忆化。

memo

工作原理

  1. 创建不会重新渲染的组件

    • memo用于创建一个组件,当其父组件重新渲染时,如果传入的props在两次渲染之间没有变化,那么这个被memo包裹的组件将不会重新渲染。
  2. 只影响props

    • memo只对组件的props进行比较。如果一个被memo包裹的组件即使在props没有变化的情况下,如果它自己的state发生了变化,或者它的context发生了变化,它仍然会重新渲染。

案例

import React, { useState, useContext } from 'react';

const MyContext = React.createContext();

const ChildComponent = React.memo(({ name }) => {
    console.log('ChildComponent rendered');
    return <div>{name}</div>;
});

const ParentComponent = () => {
    const [count, setCount] = useState(0);
    const value = useContext(MyContext);

    return (
        <div>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            <ChildComponent name="Alice" />
        </div>
    );
};

在这个示例中:

  • 当 count 增加时,ParentComponent 重新渲染,但由于 ChildComponent 的 props(name)没有变化,因此它不会重新渲染。
  • 如果 ChildComponent 内部使用了 state 或 context,并且这些值发生了变化,那么即使 props 不变,它仍然会重新渲染。

局限性

1. 每次渲染都会重新创建所有内容

在 React 中,每次组件渲染时,都会重新执行组件函数。这意味着:

  • 函数和对象的重新实例化: 每次渲染都会创建新的函数和对象实例。例如,如果在组件内部定义了一个函数或对象,它们在每次渲染时都会被重新创建。
  • 对象和函数的引用比较: 在 JavaScript 中,对象和函数是引用类型,即使它们的内容相同,它们在内存中的地址也是不同的。两个看起来相同的对象(如 {})实际上是不同的实例。

2. 传递对象或函数作为 props

当父组件将对象或函数作为 props 传递给子组件时:

  • 新实例导致重新渲染: 由于每次渲染时都会创建新的对象或函数实例,子组件会将这些新实例视为不同的 props,从而触发重新渲染。这种情况下,即使这些 props 的值没有变化,子组件也会认为它们已改变。

3. React.memo 的作用

React.memo 是一种优化技巧,通过比较 props 来决定是否重新渲染组件:

  • 防止不必要的渲染: 如果传递给子组件的 props 没有变化,React.memo 可以防止子组件不必要的重新渲染。
  • 引用类型问题: 如果 props 是对象或函数,即使它们的值没有变化,由于它们是新创建的实例,memo 也会认为 props 已经改变,从而导致子组件重新渲染。

useMemo与useCallback

工作原理

缓存机制

  • 记忆化: 当你将对象或函数传递给 useMemo 或 useCallback 时,这些值或函数会被存储在内存中(即被“缓存”)。在随后的重新渲染中,只要依赖项(即“输入”)没有变化,就会返回缓存的值或函数
  • 依赖数组useMemo 和 useCallback 都接受一个依赖数组,这与 useEffect 的工作方式类似。依赖数组用于追踪哪些值或状态影响到了记忆化的值或函数。如果依赖数组中的任何一个值发生变化,那么记忆化的值或函数就会被重新创建。这是通过比较新旧依赖项来实现的,只有当依赖项发生变化时,才会触发重新计算。

使用场景

1. 防止不必要的渲染

  • 与 React.memo 一起使用: 可以记忆化 props,以避免不必要的组件重新渲染。当子组件使用 React.memo 包装时,如果父组件传递给子组件的函数是通过 useCallback 缓存的,子组件就不会因为父组件的重新渲染而不必要地重新渲染。

2. 避免昂贵的重新计算

  • 使用 useMemo: 当需要缓存计算成本较高的值时,使用 useMemo 可以确保只有在其依赖项发生变化时才会重新计算这些值,从而节省性能。例如,在处理大型数据集时,可以避免每次渲染都进行复杂的数据处理。

3. 记忆化在其他 Hook 的依赖数组中使用的值

  • 避免无限循环: 当某个值被用作其他 Hook(如 useEffect)的依赖数组中的项时,使用 useMemo 或 useCallback 来记忆化这个值可以避免无限循环的副作用。例如,如果在 useEffect 的依赖数组中直接使用一个会不断变化的值,可能会导致无限循环的副作用执行。通过记忆化这个值,你可以确保只有当值实际改变时,副作用才会重新运行。

区别

1. 用途与缓存内容不同

  • useMemo: 用于缓存复杂函数的计算结果或者构造的值。它返回缓存的结果。
  • useCallback: 用于缓存函数本身,确保函数的引用在依赖没有改变时保持稳定。

2. 性能开销

  • 开销考虑: 使用这两个 Hook 会引入额外开销,因为它们需要存储和检索缓存。如果不在性能敏感场景中,过度使用可能导致反效果。因此,应根据具体情况合理选择是否使用。

3. 实现原理

  • 从实现上看,useCallback(fn, deps) 实际上是 useMemo(() => fn, deps) 的语法糖。这意味着它们在内部实现上非常相似,但用途不同。

案例

useCallback

import React, { useState, useCallback } from 'react';

const ChildComponent = React.memo(({ onClick }) => {
    console.log('ChildComponent rendered');
    return <button onClick={onClick}>Click Me</button>;
});

const ParentComponent = () => {
    const [count, setCount] = useState(0);

    const handleClick = useCallback(() => {
        console.log('Button clicked');
    }, []); // 确保 handleClick 在 count 不变时保持稳定

    return (
        <div>
            <h1>Count: {count}</h1>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            <ChildComponent onClick={handleClick} />
        </div>
    );
};

useMemo 

import React, { useState, useMemo } from 'react';

const ExpensiveComponent = ({ data }) => {
    const processedData = useMemo(() => {
        return expensiveCalculation(data);
    }, [data]);

    return <div>{processedData}</div>;
};

const expensiveCalculation = (data) => {
    // 模拟昂贵计算
    return data.reduce((acc, item) => acc + item, 0);
};

注意: 对于state的set函数来说,它不需要useCallback函数,它本身随着渲染就不会改变地址。

减小Bundle大小 

Bundle与Bundle分别是什么?

代码分割

代码分割(Code splitting)是一种在现代前端工程中常用的优化技术,它允许将应用程序的代码库分割成多个小块(bundles),这些小块可以独立于主代码库进行加载。这种方法特别适用于大型应用程序,可以显著提高加载速度和性能

 路由级别代码分割

转变为

Suspense是一个用于处理异步组件加载的组件。它允许你定义一个边界,当其子组件在加载过程中遇到异步操作(如数据获取)而暂停时,可以显示一个备用内容(fallback)。然后Suspense与React的lazy函数结合使用,实现组件的懒加载。这种方式允许你在组件首次渲染时自动触发组件的加载,同时在加载过程中显示一个占位符。

因为懒加载的元素,当你切换到Login或者Pricing等页面的时候, 对应的js的代码块才刚刚从服务器下载过来,需要时间到达客户端。这个时候suspense组件就起到了作用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值