Reactプロジェクトでは、反射的にuseEffectを使用することがよくありますが、このアプローチは時としてコードを不必要に複雑にする可能性があります。この記事では、useEffectが不要なケースと、その誤用を避けてコードを最適化する方法について見ていきます。
useEffectの役割を理解する
useEffectフックは、サブスクリプションやメインのレンダリングフロー外で行う必要のある操作などの副作用を管理するのに有用です。しかし、一般的なユースケースの中には、useEffectなしで簡素化できるものがあります。
1. useEffectを使用しない状態の初期化
コンポーネントのマウント時にpropsや初期値に基づいて状態を初期化したい場合がよくあります。この状態を設定するためにuseEffectを使用する代わりに、useStateで直接行うことができ、コードがより明確になり、不必要な呼び出しを防ぐことができます。
useEffectを使用する悪い例:
1import { useState, useEffect } from "react";
2
3interface GreetingProps {
4 name: string;
5}
6
7function Greeting({ name }: GreetingProps) {
8 const [greeting, setGreeting] = useState<string>("");
9
10 useEffect(() => {
11 setGreeting(`Hello, ${name}`);
12 }, [name]);
13
14 return <h1>{greeting}</h1>;
15}
useEffectを使用しない良い例:
ここでは、useStateで関数を使用してgreeting状態を直接初期化できます:
1import { useState } from "react";
2
3interface GreetingProps {
4 name: string;
5}
6
7function Greeting({ name }: GreetingProps) {
8 const [greeting] = useState<string>(() => `Hello, ${name}`);
9
10 return <h1>{greeting}</h1>;
11}
このアプローチにより、コードがより簡潔になり、アプリケーションを遅くする可能性のある余分な効果を追加することを避けられます。
2. 派生値の計算
状態やpropsに基づいて値を計算する必要がある場合は、useEffectではなく、派生変数やmemoを使用することを検討してください。
useEffectを使用する例(避けるべき):
1import { useState, useEffect } from "react";
2
3interface CartItem {
4 price: number;
5}
6
7interface CartProps {
8 items: CartItem[];
9}
10
11function Cart({ items }: CartProps) {
12 const [total, setTotal] = useState<number>(0);
13
14 useEffect(() => {
15 setTotal(items.reduce((sum, item) => sum + item.price, 0));
16 }, [items]);
17
18 return <p>Total: ${total}</p>;
19}
useEffectを使用しない解決策:
ここでは、total値をレンダリング中に直接計算でき、useEffectに依存する必要はありません:
1interface CartItem {
2 price: number;
3}
4
5interface CartProps {
6 items: CartItem[];
7}
8
9function Cart({ items }: CartProps) {
10 const total = items.reduce((sum, item) => sum + item.price, 0);
11
12 return <p>Total: ${total}</p>;
13}
この方法は、よりクリーンなだけでなく、追加の状態管理が不要になります。
3. 重い計算のためのuseMemoの使用
高コストな計算や複数の変数に依存する計算には、状態を管理するためのuseEffectを必要とせず、各レンダリングでロジックを再実行することを避けるためにuseMemoを使用します。
useEffectを使用する例:
1import { useState, useEffect } from "react";
2
3interface ExpensiveCalculationProps {
4 num: number;
5}
6
7function ExpensiveCalculation({ num }: ExpensiveCalculationProps) {
8 const [result, setResult] = useState<number>(0);
9
10 useEffect(() => {
11 const computation = heavyComputation(num);
12 setResult(computation);
13 }, [num]);
14
15 return <p>Result: {result}</p>;
16}
17
18function heavyComputation(num: number): number {
19 // 高コストな計算をシミュレート
20 return num * 2; // 簡略化した例
21}
useMemoを使用した最適化アプローチ:
1import { useMemo } from "react";
2
3interface ExpensiveCalculationProps {
4 num: number;
5}
6
7function ExpensiveCalculation({ num }: ExpensiveCalculationProps) {
8 const result = useMemo(() => heavyComputation(num), [num]);
9
10 return <p>Result: {result}</p>;
11}
12
13function heavyComputation(num: number): number {
14 // 高コストな計算をシミュレート
15 return num * 2; // 簡略化した例
16}
ここでは、useMemoを使用することで、効果を管理する必要なく結果をメモ化でき、コンポーネントをより予測可能で効率的にします。
4. 制御されたコンポーネントのプロパティの同期
制御された入力の管理には、親コンポーネントの状態と入力値を同期させるためにuseEffectを使用する必要はありません。単にpropsとコールバック関数を使用して、この状態を効率的に管理できます。
useEffectを使用する例(不要):
1import { useState, useEffect } from "react";
2
3interface TextInputProps {
4 value: string;
5 onChange: (value: string) => void;
6}
7
8function TextInput({ value, onChange }: TextInputProps) {
9 const [text, setText] = useState<string>(value);
10
11 useEffect(() => {
12 setText(value);
13 }, [value]);
14
15 return <input value={text} onChange={(e) => onChange(e.target.value)} />;
16}
useEffectを使用しない解決策:
1interface TextInputProps {
2 value: string;
3 onChange: (value: string) => void;
4}
5
6function TextInput({ value, onChange }: TextInputProps) {
7 return <input value={value} onChange={(e) => onChange(e.target.value)} />;
8}
propsを直接使用することで、コードが簡素化され、不必要な状態更新を減らすことでパフォーマンスが向上します。
5. レンダリング中のDOM直接操作
状態に基づいてスタイルやクラスを適用したい場合、useEffectを使用する必要はほとんどありません。Reactはレンダリング中にこの種の同期をうまく処理します。
useEffectを使用する例:
1import { useState, useEffect } from "react";
2
3interface HighlightedTextProps {
4 isHighlighted: boolean;
5 text: string;
6}
7
8function HighlightedText({ isHighlighted, text }: HighlightedTextProps) {
9 const [className, setClassName] = useState<string>("");
10
11 useEffect(() => {
12 setClassName(isHighlighted ? "highlight" : "");
13 }, [isHighlighted]);
14
15 return <span className={className}>{text}</span>;
16}
useEffectを使用しない解決策:
1interface HighlightedTextProps {
2 isHighlighted: boolean;
3 text: string;
4}
5
6function HighlightedText({ isHighlighted, text }: HighlightedTextProps) {
7 return <span className={isHighlighted ? "highlight" : ""}>{text}</span>;
8}
結論
useEffectを避けることで、多くの場合コードを簡素化し、Reactアプリケーションのパフォーマンスを向上させることができます。効果を使用する理由を再評価することで、より明確なコンポーネントを作成し、依存関係の設定ミスに関連するエラーを避けることができます。重要なのは、副作用なしでニーズを満たすために、状態、派生値、またはuseMemoで十分なケースを特定することです。