Reactでコンポーネントの無駄な再レンダリングを防ぐテクニック

Reactは、ユーザーインターフェースを構築するための人気のあるライブラリですが、コンポーネントの再レンダリングが頻繁に発生することがあります。無駄な再レンダリングは、アプリケーションのパフォーマンスを低下させ、ユーザー体験を損なう可能性があります。

本記事では、Reactでコンポーネントの無駄な再レンダリングを防ぐためのテクニックを詳しく解説します。

1. 再レンダリングの基本理解

Reactコンポーネントは、以下の条件で再レンダリングされます。

  • Stateの変更
    • コンポーネントの内部状態が変更されると、そのコンポーネントは再レンダリングされます。
  • Propsの変更
    • 親コンポーネントから渡されるpropsが変更されると、子コンポーネントも再レンダリングされます。
  • 親コンポーネントの再レンダリング
    • 親コンポーネントが再レンダリングされると、その子コンポーネントも再レンダリングされます。

これらの条件を理解することで、無駄な再レンダリングを防ぐための対策を講じることができます。

2. React.memoを活用する

React.memoは、コンポーネントの再レンダリングを防ぐための高階コンポーネントです。React.memoを使用することで、propsが変更されない限り、コンポーネントは再レンダリングされません。

使用例

以下は、React.memoを使用したコンポーネントの例です。

import React, { useState } from 'react';

const Child = React.memo(({ count }) => {
  console.log('Child component rendered');
  return <div>Count: {count}</div>;
});

const Parent = () => {
  const [count, setCount] = useState(0);
  const [otherState, setOtherState] = useState(false);

  return (
    <div>
      <Child count={count} />
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
      <button onClick={() => setOtherState(!otherState)}>Toggle Other State</button>
    </div>
  );
};

export default Parent;

この例では、Childコンポーネントはcountが変更されたときのみ再レンダリングされます。otherStateが変更されても、Childは再レンダリングされません。

3. useCallbackを利用する

useCallbackは、関数をメモ化するためのフックです。これにより、親コンポーネントが再レンダリングされても、同じ関数を再利用することができます。これにより、子コンポーネントが無駄に再レンダリングされるのを防ぐことができます。

使用例

以下は、useCallbackを使用した例です。

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

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

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

  const handleClick = useCallback(() => {
    console.log('Button clicked');
  }, []);

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

export default Parent;

この例では、handleClick関数はuseCallbackを使用してメモ化されています。これにより、Parentが再レンダリングされても、Childは再レンダリングされません。

4. useMemoを活用する

useMemoは、計算結果をメモ化するためのフックです。重い計算を行う場合に、再レンダリング時に毎回計算を行わないようにするために使用します。

使用例

以下は、useMemoを使用した例です。

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

const ExpensiveComponent = ({ number }) => {
  const computeExpensiveValue = (num) => {
    console.log('Computing expensive value');
    return num * 2; // 例としての重い計算
  };

  const result = useMemo(() => computeExpensiveValue(number), [number]);

  return <div>Computed Value: {result}</div>;
};

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

  return (
    <div>
      <ExpensiveComponent number={count} />
      <button onClick={() => setCount(count + 1)}>Increment Count</button>
    </div>
  );
};

export default Parent;

この例では、computeExpensiveValue関数はuseMemoを使用してメモ化されています。numberが変更されない限り、重い計算は再実行されません。

5. Contextの利用と再レンダリングの最適化

ReactのContextを使用する際、Contextの値が変更されると、そのContextを使用しているすべてのコンポーネントが再レンダリングされます。これを防ぐためには、Contextを分割することが有効です。

使用例

以下は、Contextを分割して再レンダリングを最適化する例です。

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

const CountStateContext = createContext();
const CountDispatchContext = createContext();

const countReducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error(`Unhandled action type: ${action.type}`);
  }
};

const CountProvider = ({ children }) => {
  const [state, dispatch] = useReducer(countReducer, { count: 0 });

  return (
    <CountStateContext.Provider value={state}>
      <CountDispatchContext.Provider value={dispatch}>
        {children}
      </CountDispatchContext.Provider>
    </CountStateContext.Provider>
  );
};

const CountDisplay = () => {
  const state = useContext(CountStateContext);
  return <h1>{state.count}</h1>;
};

const CountButtons = () => {
  const dispatch = useContext(CountDispatchContext);
  return (
    <>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
};

const App = () => (
  <CountProvider>
    <CountDisplay />
    <CountButtons />
  </CountProvider>
);

export default App;

この例では、CountStateContextCountDispatchContextを分割することで、状態の変更がそれぞれのコンポーネントに影響を与えないようにしています。

6. まとめ

Reactでコンポーネントの無駄な再レンダリングを防ぐためのテクニックについて解説しました。React.memouseCallbackuseMemo、そしてContextの分割を活用することで、パフォーマンスを向上させることができます。これらのテクニックを適切に使用することで、Reactアプリケーションの効率を高め、ユーザー体験を向上させることができるでしょう。

参考

タイトルとURLをコピーしました