1、React.memo

定义:
React 提供的一个高阶组件(Higher Order Component),用于优化函数组件的性能。可以避免不必要的渲染,从而提升性能。
作用:
在函数组件之间进行浅比较,如果组件的 props 没有发生变化,则避免重新渲染组件。
注意:

  • React.memo 默认使用浅比较,所以它只能检测 props 的值是否相等,无法检测 props 内部的深层次变化。
  • 如果函数组件的 props 是引用类型的(对象、数组),并且他们在每次渲染时都是新的实例,React.memo 可能无法工作,此时可以用 useMemo 来确保传递给子组件的 props 是相同的引用。
  • 可以使用第二个参数(一个自定义的比较函数,包含两个参数,分别是 prevPropsnextProps)来进行精细比较。

例:

const DemoComponent = (props) => <div>{ props.value }</div>;
const MemoizedComponent = React.memo(DemoComponent, (prevProps, nextProps) => {
    // 返回 true 表示 props 相等,不重新渲染
    // 返回 false 表示 props 不相等,重新渲染
    return prevProps.value === nextProps.value;
})

2、useMemo

定义:
用于在函数组件中进行过性能优化。用于缓存和记忆计算结果,避免在每次渲染时重复计算相同的值,从而提升组件性能。
使用:
useMemo 有两个参数:

  • 第一个参数是一个回调函数,该函数在每次渲染时都会被执行,你可以在这个函数中进行计算、处理逻辑或者其他操作,然后返回计算结果。
  • 第二个参数是一个依赖数组,包含影响计算结果的变量或值,当依赖数组中的值发生变化时,useMemo 会从新计算结果。如果依赖数组为空,则只在首次渲染时执行一次。
const memoizedValue = useMemo(() => {
    // 计算或处理逻辑
    return computedValue;
}, [dependencies]);

场景:

  • 避免重复计算:如果是一个计算耗时较长且不会频繁变化,可以使用 useMemo 来缓存结果,避免每次渲染时都重新计算。
  • 优化子组件的渲染:将计算结果作为 props 传递给子组件,可以避免在子组件渲染过程中重复执行相同的计算。
  • 避免不必要的副作用:如果你需要在渲染期间执行副作用(数据请求、订阅等),可以在 useMemo 中进行处理,可以确保副作用只在依赖项变化时执行。

    3、useCallback

定义:
用于优化函数组件的性能,他的主要作用是缓存函数,避免在每次渲染时重新创建新函数实例,从而减少不必要的渲染。

注意:
使用 useMemo 时,你可以将一个函数和一个依赖数组作为函数传递给他,它会返回一个经过优化的函数。这个函数会在依赖数组中的值发生变化的时候重新创建,否则会保持相同的引用。

使用:
useCallback 有两个参数:

  • callbackFunction:需要缓存的回调函数。
  • dependencies:一个数组,包含影响回调函数是否需要重新创建的值。当数组中值发生变化时,useCallback 会重新创建回调函数。如果数组为空,回调函数只会在首次渲染时创建一次。
const callbackFunction = () => {
    // 回调函数逻辑
};
const memoizedCallback = useCallback(callbackFunction, [dependencies])

使用场景:

  • 避免子组件不必要的重新渲染:将回调函数传递给子组件是,可以使用 useCallback 来确保子组件不会因为父组件重新渲染而重新创建回调函数。
  • 避免不必要的副作用:如果回调函数包含副作用(数据请求、订阅),使用 useCallback 可以确保副作用只在依赖项变化时执行。

缺陷:

  • 内存消耗:使用 useCallback 会导致函数的缓存,这意味着每当依赖发生变化,都会创建一个新的函数引用,如果在渲染期间频繁创建大量的函数,可能会增加内存消耗。
  • 逻辑复杂:useCallback 适用于缓存简单的回调函数,如果需要缓存具有复杂逻辑的函数,可能会导致代码变得难以理解和维护。
  • 额外的函数调用:使用 useCallback 可以避免在每次渲染时重新创建函数,但在某些情况下,可能会导致额外的函数调用,因为记忆化函数的引用发生变化时,使用该函数的组件可能会重新渲染,即使依赖项没有真正发生变化。

避免使用匿名函数:

  • 重新渲染的触发:每次父组件重新渲染时,如果传递给子组件的 props 中包含匿名函数,这些匿名函数会被重新创建,导致子组件的重新渲染,即使其他 props 没有变化。
  • 额外的内存消耗:匿名函数的创建会导致额外的内存开销,每次渲染都会生成新的函数实例,可能在大型应用中累积成性能问题。
  • 性能下降:由于匿名函数的频繁创建和销毁,可能会导致页面的性能下降,尤其是在需要频繁渲染的情况下。

4、useRef

定义:
用于在函数组件中存储和访问可变的引用值。与 useState 不同,useRef 不会引发组建的重新渲染,主要用于处理与界面渲染无关的数据。
用法:

    const refContainer = useRef(initialValue);
  • refContainer:一个包含 current 属性的对象,current 属性的值为 initialValue
  • initialValue:作为初始值传递给 useRef 的值。

用途:

  • 保留引用:useRef 用于保留引用,例如:保存 DOM 元素的引用或其他不需要触发重新渲染的值。
  • 获取最新值:由于 useRef 的值在重新渲染之间保持稳定,它长用于存储任何需要在渲染周期之间保持不变的数据。
  • 触发非渲染副作用:useRef 可以用于触发非渲染的副作用,例如获取上一次渲染的状态或引用。

例:

import React, { useRef, useEffect } from "react";
function Timer() {
    const intervalRef = useRef(null);
    useEffect(() => {
        intervalRef.current = setInterVal(() => {
            console.log("Timer tick");
        }, 1000);
        retturn () => {
            clearInterVal(intervalRef.current);
        }
    }, []);
    return <div>Timer Component</div>;
}

在上面的例子中,intervalRef 用于存储定时器的引用,以便在组件卸载时清除定时器。由于 useRef 的值在重新渲染之间保持稳定,即使组件重新渲染,intervalRef 的值仍然是上一次渲染时的引用。

5、useLayoutEffect

定义:
类似于 useEffect ,在组件渲染完成后,但在浏览器执行下一次绘制之前同步调用副作用函数。

useLayoutEffectuseEffect 的区别:

  • useEffect 是组件渲染完成后异步调用副作用的函数。
  • useLayoutEffect 是在 DOM 更新之后,浏览器绘制之前的同步调用副作用函数。好处是可以更加方便的修改 DOM ,获取 DOM 信息,这样浏览器只会绘制一次,可以避免浏览器再次回流和重绘。

如果你在 useEffect 的初始化渲染中修改了展示的数据或者 css 样式,那么很有可能会重复渲染导致闪屏,可以使用 useLayoutEffect

useLayoutEffectuseEffect 之前执行。

用途:
由于 useLayoutEffect 的副作用函数是同步执行的,它会阻塞浏览器的渲染过程。在 useLayoutEffect 中进行了 DOM 操作或读取 DOM 布局信息,会在浏览器进项下一次绘制之前立即生效,使得 useLayoutEffect 适合执行需要 DOM 布局信息的副作用操作,例如:测量元素的尺寸或者位置,根据结果进行相应的操作。

例:

import React, { useState, useLayoutEffect } from "react";

function MyComponent() {
    const [width, setWidth] = useState(0);
    
    useLayoutEffect(() => {
        function updateWidth() {
            const element = document.getElementById("my-element");
            if(element) {
                setWidth(element.offsetWidth);
            }
        };
        window.addEventListener("resize", updateWidth);
        updateWidth();

        return () => {
            window.removeEventListener("resize", updateWidth);
        };
    }, []); // 依赖数组为空,只在组件挂载和卸载的时候执行一次;

    return (
        <div>
            <p>width: { width }</p>
            <div id="my-element">Some content</div>
        </div>
    )
}

上述示例中,useLayoutEffect 用于测量元素的宽度,并在窗口大小发生变化时更新宽度。副作用函数中用了 DOM 操作来获取元素的宽度,并使用 useState 来更新组件状态。

6、React.Fragment

定义:
React 提供的一个组件,用于在不引入多余的 DOM 元素的情况下包裹多个子元素。主要作用在 JSX 中创建一个“虚拟”的父容器,用于包裹多个子元素,而不会在最终渲染的 DOM 结构中添加额外的节点。

例:

return <React.Fragment>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</React.Fragment>
// 简化 ↓↓↓
return <>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
</>

优点:
减少 DOM 结构:使用 React.Fragment 可以避免在最终渲染的 DOM 中添加多余的父元素,减少不必要的 DOM
不影响样式和布局:由于 React.Fragment 不会在渲染中生成,所以不影响样式和布局。
提高性能:不生成额外的 DOM 元素,有助于提高性能。
语法简洁:简化写法,易阅读。

7、React.lazy

定义:
React 提供的特性函数,用于实现组件的懒加载,优化性能。允许你在需要的时候才加载组件,而不是初始加载时将所有的代码都打包在一起。

例:

const MyComponent = React.lazy(() => import("./MyComponent"));

说到懒加载,不得不提一下 Suspense。
Suspense 是一个组件,它可以包裹在需要异步加载的组件外部,以指定在加载组件时如何显示加载状态或者备用内容。使用 Suspense 的时候可以延迟组件的渲染,知道实际需要再出现。

例:

import React, { Suspense } from "react";

function App() {
    return (
        <div>
            <Suspense fallback={<div> loading... </div>}>
                <MyComponent/>
            </Suspense>
        </div>
    )
}

当页面渲染时,如果 MyComponent 没有加载完成,Suspense 将会显示 fallback 中定义的加载状态,知道组件加载完成。
React.lazy() 和 Suspense 目前只支持默认导出的组件。如果需要导入命名导出的组件,你可以这样:

const MyComponent = React.lazy(() => import("./MyComponent")).then(module => ({ default: module.MyComponent}));

例:

import AnotherComponent from "./another-component";
// 延迟加载不是立即需要的组件
const MUITooltip = React.lazy(() => import("./tooltip"));

function Tooltip({ children, title }) => {
    return (
        <React.Suspense fallback={ children }>
            <MUITooltip title={title}>
                { children }
            </MUITooltip>
        </React.Suspense>
    )
}

function Component(props) {
    return (
        <Tooltip>
            <AnotherComponent/>
        </Tooltip>
    )
}

8、useContext

定义:
用于在函数组件中访问 React 的上下文(context)。上下文是一种在组件树中共享数据的机智,可以避免数据通过 props 层层传递给深层组件,建华数据共享和传递的过程。

使用:

const MyContext = React.createContext();

<MyContext.Provider value={/*传递的内容*/}>
    <App/>
</MyContext>

// 在子组件中使用上下文值
function ChildComponent() {
    const value = useContext(MyContext);
}

用途:

  • 共享数据:useContext 允许你在组件树中共享数据,而无需通过 props 传递每一个中间组件。
  • 避免prop drilling:通过 useContext,你可以避免在组件层次结构中进行多层级的 props 传递。
  • 主题和用户设置:可用于实现主题切换,用户设置等全局状态共享。
  • 解耦组件:useContext 是组件不再直接依赖于特定数据源,增加组建的可复用性和灵活性。

问题:
Context 是粗颗粒度的,只要其中一个值发生变化都会更新用到该 Context 的组件。解决办法是在需要 Context 内容值进行细分,通过 props 按需传递。

必要时也要做缓存,避免更新。

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

const App = () => {
    const [visible, setVisible] = useState(false);
    const EditorContextVal = useMemo(() => ({ visible, setVisible }), [visible, setVisible]);
    return (
        <EditorContext.Provider value={EditorContextVal}>
            <Editor />
        </EditorContext.Provider>
    )
}

9、useTransition

定义:
用于在并发模式(Concurrent Mode)下,处理 UI 过度的一种方式,允许在异步操作中实现平滑的 UI 过度效果,无需在加载期间完全阻塞用户界面。对于加载资源、处理网络请求等很有用,可以保证页面的响应性。

startTransition 告诉 React 一些状态更新具有较低优先级,即每个其他状态更新或 UI 渲染触发器,具有较高的优先级。

使用:

const [isPending, startTransition] = useTransition();
  • isPending: 是否有过度正在进行。
  • startTransition:用于触发过渡,接受一个回调函数作为参数,这个回调函数中的操作会被视为异步操作,可以触发 UI 过渡。

场景:

  • 异步操作:当需要在进行异步操作时,能够在过渡期间保持界面的响应,避免页面卡顿。
  • 平滑过渡:当异步操作会导致页面元素的变化,希望在数据加载期间进行平滑的界面过渡效果。

例:

import { useTransition, setState } from "react";

function App() {
    const [isPending, startTransition] = useTransition();
    const [filterTerm, setFilterTerm] = useState("");

    const filteredProducts = filterProducts(filterTerm);

    function updateFilterHandler(event) {
        startTransition(() => {
            setFilterTerm(event.target.value);
        })
    }

    return (
        <div id="app">
            <input type="text" onChange={updateFilterHandler} />
            { isPending && <p style={{ color: "white" }}> Updating list.. </p>}
            <ProductList products={filteredProducts} />
        </div>
    )
}

14、useDefferdValue

定义:
用于在并发模式(Concurrent Mode)下处理 UI 过渡的方式,通过 useDefferdValue 允许变量延时更新,同事接受一个可选的延迟更新最大值。React 将尝试尽快更新延迟值,如果在给定的 timeoutMs 期限内未能完成,他将强制更新。

const defferValue = useDeferredValue(value, { timeoutMs: 1000 });

useDeferredValue 可以在并发渲染时调整优先级,用于延迟计算逻辑比较复杂的状态,其他组件优先渲染,等待这个状态更新完再渲染。

作用:
useDefferedValue 和 useTransition 一样,都是用于不阻塞 UI 的情况下更新状态,但是使用场景不同。 useTransition 是昂给你能够完全控制哪个 更新操作 应该以一个比较低的优先级被调用。(从父级传递来的状态可能有偏差。

例:
我们有一个 0 - 19999 的数组,以列表的形式展示,文本框输入的内容用来过滤列表。

import { useState, useMemo, useDeferredValue } = form "react";

const numbers = { ...new Array(200000).keys() };

// 父组件
export default function App() {
    const [query, setQuery] = useSttate("");
    
    const handdleChange = (e) => {
        setQuery(e.target.value);
    };

    return (
        <div>
            <input type={number} onChange={handleChange} value={query}
            <List query={query} />
        </div>
    )
}

// 子组件
function List(props) {
  const { query } = props;
  const defQuery = useDeferredValue(query);

  const list = useMemo(
    () =>
      numbers.map((i, index) =>
        defQuery ? (
          i.toString().startsWith(defQuery) && <p key={index}>{i}</p>
        ) : (
          <p key={index}>{i}</p>
        ),
      ),
    [defQuery],
  );

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

九先生
48 声望3 粉丝

打工人,打工魂!


« 上一篇
浏览器存储