9. React 性能优化 之 合适的渲染策略

3 阅读4分钟

React 性能优化 之 合适的渲染策略

React 性能优化 是指通过采取一些 技术手段减少 不必要的 渲染提升 应用的 响应速度用户体验。随着 React 应用复杂度 的增加,性能优化 变得尤为重要。React 提供了一些 内置的 工具 和 方法 来帮助开发者 优化性能。

1 避免 不必要的 重新渲染

1.1 使用 React.memo

React.memo 是一个 高阶组件(HOC),用于优化 函数组件。它通过对比 当前的 props上次的 props,决定是否重新 渲染组件。若 props 没有变化,则会 跳过重新渲染

import React from 'react';

const MyComponent = React.memo(({ name }) => {
  console.log('Rendering:', name);
  return <h1>{name}</h1>;
});

function App() {
  return (
    <div>
      <MyComponent name="Alice" />
      <MyComponent name="Bob" />
    </div>
  );
}

export default App;

在这个例子中,只有 name 属性 变化时,MyComponent 才会 重新渲染。

1.2 使用 PureComponent

PureComponent 是一个 类组件,它只会在 propsstate 改变时 才会重新渲染。与 React.memo 类似,PureComponent 内部使用 浅比较 来决定 是否更新。

import React, { PureComponent } from 'react';

class MyComponent extends PureComponent {
  render() {
    console.log('Rendering:', this.props.name);
    return <h1>{this.props.name}</h1>;
  }
}

function App() {
  return (
    <div>
      <MyComponent name="Alice" />
      <MyComponent name="Bob" />
    </div>
  );
}

export default App;

1.3 使用 shouldComponentUpdate

类组件 中,shouldComponentUpdate 方法 可以用来 手动控制组件 是否需要 重新渲染。如果返回 false,则 React 会 跳过渲染

import React, { Component } from 'react';

class MyComponent extends Component {
  shouldComponentUpdate(nextProps) {
    // 仅当 `name` 改变时才重新渲染
    return nextProps.name !== this.props.name;
  }

  render() {
    console.log('Rendering:', this.props.name);
    return <h1>{this.props.name}</h1>;
  }
}

function App() {
  return (
    <div>
      <MyComponent name="Alice" />
      <MyComponent name="Bob" />
    </div>
  );
}

export default App;

2 延迟渲染 和 代码分割

2.1 使用 React.lazy<Suspense>

React.lazy 允许你 按需加载组件,只有当组件 实际被使用时 才加载。这对于 减少 初始加载时间 特别有效。

import React, { Suspense, lazy } from 'react';

// 使用 React.lazy 异步加载组件
const MyComponent = lazy(() => import('./MyComponent'));

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

export default App;

Suspense 组件 用于 包裹 懒加载 的组件,可以在 加载过程中 显示一个 占位符(如 加载中的 提示)。

2.2 动态导入 与 代码分割

结合 React Router动态导入,可以 按需加载 页面组件,避免 将整个应用 打包成一个 庞大的文件

import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';

const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </Router>
  );
}

export default App;

3 避免 重复渲染(避免 不必要的 props 传递)

按需更新 State

避免 在 useStatesetState更新 与当前状态 相同的值React 会检测到 值没有变化,从而 跳过渲染

import React, { useState } from 'react';

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

  const increment = () => {
    setCount((prevCount) => (prevCount === count ? prevCount : prevCount + 1));
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default App;

4 使用 虚拟化列表(Virtualized List)

渲染 大量列表项 时,React 会为 每个列表项 都 渲染 一个组件。这会导致 性能问题虚拟化 技术 可以只渲染 当前在屏幕上的 部分元素,从而 提升性能

4.1 使用 react-windowreact-virtualized

这两个库用于实现 虚拟化列表,能够显著提高 渲染 大规模数据 的效率。

import React from 'react';
import { FixedSizeList as List } from 'react-window';

function App() {
  const items = new Array(1000).fill(null).map((_, index) => `Item ${index + 1}`);

  return (
    <List
      height={500}
      itemCount={items.length}
      itemSize={35}
      width={300}
    >
      {({ index, style }) => (
        <div style={style}>{items[index]}</div>
      )}
    </List>
  );
}

export default App;

5 使用 合适的 渲染策略

5.1 使用 useMemouseCallback

  • useMemo:用于 缓存 计算结果,避免 重复计算
  • useCallback:用于 缓存 函数实例,避免 不必要的 函数重建
// 示例:使用 `useMemo` 缓存计算结果
import React, { useState, useMemo } from 'react';

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

  const expensiveComputation = useMemo(() => {
    console.log('Expensive computation running...');
    return count * 2;
  }, [count]);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Expensive Computation: {expensiveComputation}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

export default App;
// 示例:使用 `useCallback` 缓存回调函数
import React, { useState, useCallback } from 'react';

function Child({ onClick }) {
  console.log('Child rendered');
  return <button onClick={onClick}>Click me</button>;
}

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

  const increment = useCallback(() => {
    setCount((prevCount) => prevCount + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <Child onClick={increment} />
    </div>
  );
}

export default App;

6 合理使用 Context

React Context 是一种 传递 全局状态 的方法,但如果传递的 数据频繁变化,可能导致 所有使用该 Context 的组件 重新渲染,影响性能。为了解决这个问题,可以 分层细化 Context 的使用,使其只影响 需要的部分

6.1 优化 Context 的使用

通过将多个 独立的状态 分割不同的 Context 中,减少 不必要的渲染。

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

// 分割 Context
const CountContext = createContext();
const NameContext = createContext();

function App() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('Alice');

  return (
    <CountContext.Provider value={{ count, setCount }}>
      <NameContext.Provider value={{ name, setName }}>
        <Child />
      </NameContext.Provider>
    </CountContext.Provider>
  );
}

function Child() {
  const { count, setCount } = useContext(CountContext);
  const { name, setName } = useContext(NameContext);

  return (
    <div>
      <p>Count: {count}</p>
      <p>Name: {name}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setName('Bob')}>Change Name</button>
    </div>
  );
}

export default App;