likes
comments
collection
share

react中性能优化的方法

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

一、影响浏览器渲染效率的因素有哪些?

1. DOM操作:对DOM进行频繁的操作可能会导致性能问题。每次更改DOM时,浏览器都必须重新计算布局和绘制,这可能会导致性能下降。为了避免这种情况,可以尝试将多个DOM更改合并为单个更改,或者使用DocumentFragment来一次性添加多个元素。

2. 重绘和回流:重绘和回流是浏览器重新计算布局和绘制的过程。重绘是指浏览器重新绘制元素的外观,而回流是指浏览器重新计算元素的位置和大小。这些过程可能会导致性能问题,因为它们需要大量的计算资源。为了避免这种情况,可以尝试使用CSS动画代替JavaScript动画,或者使用transform和opacity属性来避免回流。

3. JavaScript执行:JavaScript执行可能会导致性能问题,因为它需要大量的计算资源。为了避免这种情况,可以尝试将计算移到Web Worker中,或者使用requestAnimationFrame来优化动画。

4. 图像大小:图像大小可能会影响DOM渲染的性能。大型图像需要更长的时间来下载和解码,这可能会导致性能问题。为了避免这种情况,可以尝试使用适当大小的图像,并使用srcset属性来提供不同大小的图像。

二、在 React 中性能优化的hook使用原则

在 React 中,使用 React.memouseCallbackuseMemo 等优化性能的 Hook 应该是在需要解决特定问题时才去应用,而不是尽可能多的使用。 虽然这些 Hook 可以帮助我们避免不必要的渲染和重复计算,但是它们也会增加代码的复杂度并且在某些情况下会产生负面影响。 因此,在开发过程中,我们应该遵循以下原则:

  1. 首先,我们应该专注于编写可读性更好、可维护性更高的代码。在代码编写完成后,我们可以使用性能优化的 Hook 来解决性能问题
  2. 只有在组件的渲染性能确实受到了影响时,才应该考虑使用 React.memouseCallbackuseMemo等 Hook 进行优化。
  3. 在应用性能优化 Hook 时,我们需要仔细分析组件的渲染逻辑和数据依赖关系,确保使用这些 Hook 不会产生负面影响。

三、React中如何使用hook

1.不要过多使用useState

过多使用 useState 钩子的示例,会导致不必要的复杂性,使代码难以阅读和维护。

使用过多的useState可能会导致性能问题。每次调用useState都会创建一个新的状态变量,并且每次更新状态都会触发组件重新渲染。如果组件中有很多useState,那么每次更新都会导致大量的重复渲染,从而影响性能。

import React, { useState } from 'react';

function Example() {
  const [count, setCount] = useState(0);
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  const [address, setAddress] = useState('');

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <input value={age} onChange={(e) => setAge(e.target.value)} />
      <input value={address} onChange={(e) => setAddress(e.target.value)} />
    </div>
  );
}

以下进行优化:


import React, { useState } from 'react';

function Example() {
  // 使用单个对象存储所有状态变量,而不是使用多个 useState 钩子。

  const [state, setState] = useState({
    count: 0,
    name: '',
    age: 0,
    address: ''
  });

  return (
    <div>
      <p>你点击了 {state.count} 次</p>
      <button onClick={() => setState({ ...state, count: state.count + 1 })}>
        点我
      </button>
      <input value={state.name} onChange={(e) => setState({ ...state, name: e.target.value })} />
      <input value={state.age} onChange={(e) => setState({ ...state, age: e.target.value })} />
      <input value={state.address} onChange={(e) => setState({ ...state, address: e.target.value })} />
    </div>
  );
}

2.管理多个状态变量useReducer

如果需要在组件中管理多个状态变量,可以考虑使用useReduceruseReducer是另一种React Hook,它可以管理复杂的状态逻辑,并且可以更好地控制组件的渲染。

在这个示例中使用dispatch函数来更新用户资料表单的各个字段。当用户提交表单时,我们可以将表单数据发送到服务器。

import React, { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'updateName':
      return { ...state, name: action.payload };
    case 'updateAge':
      return { ...state, age: action.payload };
    case 'updateAddress':
      return { ...state, address: action.payload };
    default:
      throw new Error();
  }
}

function Profile() {
  const [state, dispatch] = useReducer(reducer, {
    name: '',
    age: 0,
    address: ''
  });

  const handleSubmit = (e) => {
    e.preventDefault();
    // 发送表单数据到服务器
  };

  return (
    <form onSubmit={handleSubmit}>
      <label>
        姓名:
        <input
          type="text"
          value={state.name}
          onChange={(e) =>
            dispatch({ type: 'updateName', payload: e.target.value })
          }
        />
      </label>
      <label>
        年龄:
        <input
          type="number"
          value={state.age}
          onChange={(e) =>
            dispatch({ type: 'updateAge', payload: e.target.value })
          }
        />
      </label>
      <label>
        地址:
        <input
          type="text"
          value={state.address}
          onChange={(e) =>
            dispatch({ type: 'updateAddress', payload: e.target.value })
          }
        />
      </label>
      <button type="submit">提交</button>
    </form>
  );
}

在上面的示例中,我们使用useReducer来管理一个用户资料表单的状态。reducer函数接收当前状态和一个操作,然后返回新的状态。useReducer返回一个包含当前状态和dispatch函数的数组。dispatch函数用于触发操作并更新状态。

使用useReducer的主要优点是可以更好地管理多个状态变量,同时也可以更好地控制状态变量的更新。但是,使用useReducer也有一些弊端:

  1.  代码复杂度:相比于使用useState,使用useReducer需要编写更多的代码,包括reducer函数和各种action类型。这会增加代码的复杂度和维护成本。

  2.  学习成本:相比于useState,useReducer的使用方法更为复杂,需要理解reducer函数和dispatch函数的概念。

  3. 性能问题:在某些情况下,使用useReducer可能会导致性能问题。例如,在更新状态变量时,useReducer会创建一个新的状态对象,并将其与旧的状态对象进行比较。如果状态对象很大,这可能会导致性能问题。

3. 使用React.memo优化组件

React.memo 是一个高阶组件,可以用于优化组件的性能。使用 React.memo 可以避免组件在 props 没有变化的情况下进行重复渲染。当组件的 props 发生变化时,React.memo 会对组件进行重新渲染。 使用React.memo来优化<MyComponent/>的渲染。 当“data”prop未更改时,React.memo将防止<MyComponent/>的不必要重新呈现。 这可以提高应用程序的性能。

React.memo 的使用方法如下:

// 将创建一个名为“MyComponent”的函数组件,它接受名为“data”的prop
const MyComponent = React.memo(({ data }) => {
  // 此组件将从“data”prop中呈现项目列表
  return (
    <ul>
      {data.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  );
});

// 将创建一个名为“App”的父组件,它将呈现“MyComponent”
const App = () => {
  // 将使用项目数组初始化状态
  const [items, setItems] = React.useState([
    { id: 1, name: "Item 1" },
    { id: 2, name: "Item 2" },
    { id: 3, name: "Item 3" },
  ]);

  // 将定义一个方法,该方法将使用新项目更新状态
  const addItem = () => {
    const newItem = { id: 4, name: "Item 4" };
    setItems((prevState) => [...prevState, newItem]);
  };

  // 将使用“items”状态作为prop呈现“MyComponent”
  return (
    <div>
      <MyComponent data={items} />
      <button onClick={addItem}>添加项目</button>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById("root"));

React.memo仅应用于昂贵的组件,且在给定相同props的情况下具有相同的结果。组件依赖于外部状态或具有副作用,则可能不适合进行记忆化。

4. 使用useCallback和useMemo

React 中的 useCallbackuseMemo 都可以用于优化组件的性能。其中,useCallback 可以用于缓存函数,而 useMemo 可以用于缓存值。这两个 Hook 都可以避免重复计算和创建对象,从而提高组件的性能。

在这种情况下,我们使用 useCallback 来记忆传递给 ItemList 组件的 handleItemClick 函数。这可以防止 ItemList 组件由于 ParentComponent 中的状态更新而不必要地重新渲染。

useCallback 的使用方法如下:

// 定义一个渲染项目列表的组件
function ItemList({ items, onItemClick }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id} onClick={() => onItemClick(item)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

// 定义一个渲染 ItemList 组件的父组件
function ParentComponent() {
  const [selectedItem, setSelectedItem] = useState(null);

  // 定义一个函数,该函数将作为一个 prop 传递给 ItemList 组件
  // 当单击项目时,将调用此函数
  // 使用 useCallback 来记忆函数并防止 ItemList 组件不必要的重新渲染
  const handleItemClick = useCallback((item) => {
    setSelectedItem(item);
  }, []);

  // 定义要由 ItemList 组件渲染的项目数组
  const items = [
    { id: 1, name: "项目 1" },
    { id: 2, name: "项目 2" },
    { id: 3, name: "项目 3" },
  ];

  return (
    <div>
      <h1>已选择的项目:{selectedItem ? selectedItem.name : "无"}</h1>
      <ItemList items={items} onItemClick={handleItemClick} />
    </div>
  );
} 

在上面的例子中,handleClick 函数被包裹在 useCallback 函数中,这样就可以缓存函数,避免重复创建函数。

当用户在搜索框中输入关键字时,需要根据关键字实时搜索商品并展示在页面上。为了提高搜索速度,我们可以使用useMemo来缓存搜索结果,避免每次重新搜索。

useMemo 的作用:

  1. useMemo是一个hook,它可以用于缓存计算结果。
  2. 它将接收一个函数和一个依赖项数组作为参数。
  3. 当依赖项发生变化时,useMemo将重新计算函数的结果。
  4. 如果依赖项没有变化,则useMemo将返回上一次计算的结果,而不会重新计算。

下面是一个简单的示例代码:

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

function ProductList({ keyword }) {
  const [products, setProducts] = useState([]);

  // 使用useMemo缓存搜索结果
  const filteredProducts = useMemo(() => {
    return products.filter(product => product.name.includes(keyword));
  }, [products, keyword]);

  // 模拟异步获取商品列表
  useEffect(() => {
    setTimeout(() => {
      setProducts([
        { id: 1, name: 'iPhone 12' },
        { id: 2, name: 'iPad Pro' },
        { id: 3, name: 'MacBook Air' },
        { id: 4, name: 'AirPods Pro' },
      ]);
    }, 1000);
  }, []);

  return (
    <div>
      <h2>Search Results:</h2>
      <ul>
        {filteredProducts.map(product => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

function App() {
  const [keyword, setKeyword] = useState('');

  return (
    <div>
      <input type="text" value={keyword} onChange={e => setKeyword(e.target.value)} />
      <ProductList keyword={keyword} />
    </div>
  );
}

export default App;

useMemo与React.memo区别

React.memo和useMemo的区别在于它们的用途不同。

React.memo用于优化组件的渲染,而useMemo用于缓存计算结果。 React.memo将检查组件的prop是否发生变化,而useMemo将检查依赖项是否发生变化。 另外,React.memo是一个高阶组件,而useMemo是一个hook

5. 使用React.lazy和Suspense

例子:

React.lazySuspense 是 React 18 中新增的功能,可以用于按需加载组件。使用 React.lazySuspense 可以避免在应用程序启动时加载所有组件,从而提高应用程序的性能。 React.lazy Suspense 的使用方法如下:

import React, { lazy, Suspense } from 'react';
const MyComponent = lazy(() => import('./MyComponent'));
const App = () => {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <MyComponent />
      </Suspense>
    </div>
  );
};
export default App;

在上面的例子中,MyComponent 组件被包裹在 Suspense 组件中,这样就可以在组件加载完成之前显示 Loading...。

6. 使用useLayoutEffect

useLayoutEffect 是 React 18 中新增的 Hook,可以用于在渲染之前执行副作用。使用 useLayoutEffect 可以在组件更新之前修改 DOM,从而提高应用程序的性能。 useLayoutEffect 的使用方法如下:

jsxCopy code
import React, { useLayoutEffect, useRef } from 'react';
const MyComponent = () => {
  const ref = useRef(null);
  useLayoutEffect(() => {
    // 修改 DOM
    ref.current.style.backgroundColor = 'red';
  }, [/* 依赖项 */]);
  return <div ref={ref}>Hello, world!</div>;
};

在上面的例子中,useLayoutEffect 函数被用于修改 DOM,这样可以在组件更新之前修改 DOM,提高应用程序的性能。 总结: 使用 React 18 进行性能优化可以从以下方面入手:

  • 使用 React.memo 优化组件的性能。
  • 使用 useCallbackuseMemo 避免重复计算和创建对象。
  • 使用 React.lazySuspense 按需加载组件。
  • 使用 useLayoutEffect 在渲染之前执行副作用。 以上的 React Hook 在实际开发中的使用场景和使用案例还需要开发者结合具体的业务场景进行分析和实践。

7. React Fragment

React Fragment 简写方式<></>,但<>不接受键值或者属性 ,React Fragment 对比无效 HTML 的问题的<div> 元素有以下优点。

  1. React Fragment 的代码可读性更高。
  2. 因为React Fragment 有一个更小的DOM,它们渲染更快,使用更少的内存。
  3. React Fragment 允许按预期呈现 React 组件,而不会引起任何父子关系问题。
  4. Fragment 允许返回多个 JSX 元素,这解决了 react 应用程序中由每个组件只能返回一个元素的约束引起的无效 HTML标记的问题。
import "./App.css";
import React from "react";
const Table = ({ children, style }) => {
  return <div>{children}</div>;
};
const TableData = () => {
  return (
    <React.Fragment>
      <td>John Doe</td>
      <td>16</td>
      <td>Developer</td>;
    </React.Fragment>
  );
}
function App() {
  return (
    <Table>
      <tr>
        <th>Name</th>
        <th>Age</th>
        <th>Occupation</th>
      </tr>
      <TableData />
    </Table>
  );
}
export default App;

8、SomeContext.provider 与useContext

当你需要在组件树中传递数据,而不需要在每个层级手动传递props时,就可以使用context

SomeContext.Provider是一个React组件,用于创建一个新的上下文环境。它接收一个value属性,用于传递状态数据。所有在SomeContext.Provider组件内部渲染的子组件都可以通过useContext来访问这个上下文环境中的状态数据。

在下面的示例中创建了一个名为ThemeContext的上下文环境,并将其传递给ThemeContext.Provider组件的value属性。在Panel组件中,我们使用useContext来访问ThemeContext中的状态数据。

需要注意的是,useContext只能用于访问通过SomeContext.Provider传递的状态数据。如果没有在组件树中找到匹配的SomeContext.Provider,那么useContext会返回SomeContext的默认值(如果有的话)。

例子:

import { createContext, useContext } from 'react';

const ThemeContext = createContext(null);

export default function MyApp() {
    return (
        <ThemeContext.Provider value="dark">
        <Form />
        </ThemeContext.Provider>
    )
}

function Form() {
    return (
        <Panel title="Welcome">
        <Button>Sign up</Button>
        <Button>Log in</Button>
        </Panel>
    );
}

function Panel({ title, children }) {
    const theme = useContext(ThemeContext);
    const className = 'panel-' + theme;
    return (
        <section className={className}>
        <h1>{title}</h1>
        {children}
        </section>
    )
}

function Button({ children }) {
    const theme = useContext(ThemeContext);
    const className = 'button-' + theme;
    return (
        <button className={className}>
        {children}
        </button>
    );
}

参考

usecallback和usememo是不是值得大量使用?

转载自:https://juejin.cn/post/7225511164124512293
评论
请登录