likes
comments
collection
share

React中的计算属性useMemo,有哪些让人眼前一亮的特性?useMemo 是一个 React Hook,它在每次重

作者站长头像
站长
· 阅读数 24

往期文章:

useMemo简介

useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。

语法:

const cachedValue = useMemo(calculateValue, dependencies)

参数 :

  • calculateValue

要缓存计算值的函数:没有任何参数的纯函数,并且可以返回任意类型,React 将会在首次渲染时调用该函数;在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值。

  • dependencies

所有在 calculateValue 函数中使用的响应式变量组成的数组。响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。

返回值 :

在初次渲染时,useMemo 返回不带参数调用 calculateValue 的结果。

在接下来的渲染中,如果依赖项没有发生改变,它将返回上次缓存的值;否则将再次调用 calculateValue,并返回最新结果。

类比vue中的计算属性

学过vue的同学都知道,vue中的computed计算属性可以根据ref对象或者其他computed计算属性自动更新值,像这样:

// 定义 ref 对象
const count = ref(1);
const factor = ref(2);

// 计算属性:根据 count 自动计算出 doubledCount
const doubledCount = computed(() => {
  return count.value * 2;
});

// 计算属性:根据 doubledCount 和 factor 计算最终结果
const finalResult = computed(() => {
  return doubledCount.value * factor.value;
});

在 React 中,useMemo 也可以看成是一种实现计算属性的方式:

import React, { useMemo } from 'react';

function MyComponent({ value }) {
  const computedValue = useMemo(() => {
    return value * 2;
  }, [value]);

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

上面代码中的 computedValue 就类似于 Vue 中的计算属性,会根据 value 的变化动态更新。

但我们得注意,它的使用场景和机制与 Vue 中的计算属性存在一些差异。

响应式 vs. 手动依赖

  • 在 Vue 中,计算属性依赖于 Vue 的响应式系统,依赖项会自动追踪。
  • 在 React 中,useMemo 需要手动指定依赖项数组(第二个参数)。

函数式 vs. 声明式

  • Vue 的计算属性是声明式的,通常在组件定义中通过 computed 对象声明,使用起来很直观。
  • useMemo 是基于函数式编程思想的,需要在函数组件的逻辑中显式调用和定义。

应用场景

  • Vue 的计算属性通常用于根据多个数据源派生出一个新的值。
  • React 的 useMemo 更多是用于优化性能,避免在每次渲染时进行不必要的复杂计算。

性能优化

通过上面的介绍,我们可以知道, useMemo更多是用在性能优化方面。比如

  • 避免不必要的重复计算
  • 避免不必要的子组件重新渲染
  • 避免函数的重新创建

避免不必要的重复计算

假如我们有一个非常耗费性能的计算方法 expensiveResult,父组件每次更新都会触发这个方法,这个非常浪费性能的!

function ExpensiveCalculationComponent({ value }) {
  // 使用 useMemo 缓存计算结果,只有当 value 变化时才重新计算
  const expensiveResult = () => {
    const time = new Date().getTime();
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += value * i;
    }
    const endTime = new Date().getTime();
    console.log(`计算耗时: ${(endTime - time)/ 1000}s`);
    return result;
  };

  return (
    <div>
      <p>父组件传递的值value值: {value}</p>
      <p>useMemo计算的结果: {expensiveResult()}</p>
    </div>
  );
}

可以看到,每次父组件更新(即使value没更新),子组件都会被这个方法阻塞约1s。

React中的计算属性useMemo,有哪些让人眼前一亮的特性?useMemo 是一个 React Hook,它在每次重

这显然是不合理的,我们只希望value改变时,重新执行这个方法!此时,useMemo就派上用场了!

function ExpensiveCalculationComponent({ value }) {
  // 使用 useMemo 缓存计算结果,只有当 value 变化时才重新计算
  const expensiveResult = useMemo(
    () => {
      const time = new Date().getTime();
      let result = 0;
      for (let i = 0; i < 1000000000; i++) {
        result += value * i;
      }
      const endTime = new Date().getTime();
      console.log(`计算耗时: ${(endTime - time)/ 1000}s`);
      return result;
    },[value]
  )

  return (
    <div>
      <p>父组件传递的值value值: {value}</p>
      <p>useMemo计算的结果: {expensiveResult}</p>
    </div>
  );
}

可以看到,使用useMemo后,父组件更新不会导致expensiveResult重新计算,除非useMemo的依赖项value发生改变!

React中的计算属性useMemo,有哪些让人眼前一亮的特性?useMemo 是一个 React Hook,它在每次重

示例完成代码

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

function ExpensiveCalculationComponent({ value }) {
  // 使用 useMemo 缓存计算结果,只有当 value 变化时才重新计算
  const expensiveResult = useMemo(
    () => {
      const time = new Date().getTime();
      let result = 0;
      for (let i = 0; i < 1000000000; i++) {
        result += value * i;
      }
      const endTime = new Date().getTime();
      console.log(`计算耗时: ${(endTime - time)/ 1000}s`);
      return result;
    },[value]
  )

  return (
    <div>
      
      <p>父组件传递的值value值: {value}</p>
      <p>useMemo计算的结果: {expensiveResult}</p>
    </div>
  );
}

function App() {
  const [inputValue, setInputValue] = useState(1);
  const [otherValue, setOtherValue] = useState(10);

  return (
    <div>
      <h3>useMemo性能演示</h3>
      <div style={{marginBottom:'15px'}}>父组件--------------------------------------------------------</div>

      {/* 改变这个值不会触发 ExpensiveCalculationComponent 的重新计算 */}
      <button onClick={() => setOtherValue(otherValue + 1)}>
        点击重新加载父组件
      </button>

      {/* 改变 inputValue 会触发 ExpensiveCalculationComponent 的重新计算 */}
      <button onClick={() => setInputValue(inputValue + 1)}>
        点击更新value的值 ({inputValue})
      </button>
      <div style={{marginTop:'15px'}}>子组件--------------------------------------------------------</div>
      <ExpensiveCalculationComponent value={inputValue} />
    </div>
  );
}

export default App;

避免不必要的子组件重新渲染

在某些情况下,我们希望某个子组件仅在其属性变化时才重新渲染。如果该属性是通过复杂计算得来的,可以使用 useMemo 缓存该计算结果,从而避免子组件不必要的重新渲染。

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

function ChildComponent({ data }) {
  console.log('子组件渲染');
  return <div>{data.join(', ')}</div>;
}

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

  const data = useMemo(() => {
    console.log('计算中...');
    return [count, count + 1, count + 2];
  }, [count]);

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

export default ParentComponent;

解释:

  • 这里的 data 是通过 useMemo 计算得来的数组,只有当 count 变化时才会重新计算。
  • 如果没有 useMemo,每次 ParentComponent 重新渲染时,即使 count 没有变化,ChildComponent 也会因为 data 的重新创建而重新渲染。
  • 使用 useMemo 后,ChildComponent 只在 data 变化时才重新渲染。

避免函数的重新创建

在 React 中,每次组件渲染时,组件中的函数都会重新创建。

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

function ChildComponent({ onClick }) {
  console.log('子组件渲染');
  return <button onClick={onClick}>点击子组件</button>;
}

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

  const handleClick = () => {
    alert('按钮被点击!哈哈');
  };

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>增加count ({count})</button>
    </div>
  );
}

export default ParentComponent;

上述代码中,每次 ParentComponent父组件重新渲染时,handleClick 函数都会重新创建,这可能导致 ChildComponent 子组件做不必要的重新渲染,浪费性能。

如果我们把handleClick使用useMemo缓存,就会避免这个问题

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

function ChildComponent({ onClick }) {
  console.log('子组件渲染');
  return <button onClick={onClick}>点击子组件</button>;
}

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

  const handleClick = useMemo(() => {
    alert('按钮被点击!哈哈');
  },[])

  return (
    <div>
      <ChildComponent onClick={handleClick} />
      <button onClick={() => setCount(count + 1)}>增加count ({count})</button>
    </div>
  );
}

export default ParentComponent;

使用useMemo后,handleClick 只会在第一次渲染时创建一次,并在依赖项变化时(这里为空数组,不会变化)重新创建。

实际上,缓存函数我们会使用useMemo的变体:useCallback

总结

  • useMemo 可以显著优化组件的性能,特别是在有复杂计算或频繁渲染的场景中。
  • 与 Vue 的计算属性类似,useMemo 也是为了缓存计算结果,但它需要手动传递依赖项,并且是基于函数式编程的设计。
  • useCallbackuseMemo 的变体,用于缓存函数,避免不必要的函数重新创建。
转载自:https://juejin.cn/post/7402517513933488178
评论
请登录