0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React:memoとuseCallbackでリストのパフォーマンス向上

Posted at

問題

スクリーンショット 2025-01-24 0.53.28.png

上記のようなコンポーネントを作りました。
CardListコンポーネントの中でCardListItemコンポーネントが繰り返されている形です。

CardListコンポーネントではitemValuesという状態を持ちます。
もしどれかのカードの入力欄に値を入れると、親でitemValuesが更新され、全てのカードが再描画されてしまいます。

それでは、入力が実行されたカードだけ再描画できるようにしていきましょう。

リファクタ前のコード

CardList.tsx↓

import React, { useState } from "react";
import CardListItem from "./CardListItem";

const CardList = () => {
  const [itemValues, setItemValues] = useState([
    { id: 1, value: "" },
    { id: 2, value: "" },
    { id: 3, value: "" },
  ]);

  const changeValue = (id: number, value: string) => {
    setItemValues(
      itemValues.map((item) => {
        if (item.id === id) {
          item.value = value;
        }
        return item;
      })
    );
  };

  return (
    <div>
      <div className="mb-3">itemValues: {JSON.stringify(itemValues)}</div>
      <div className="w-[50%] flex gap-2">
        {itemValues.map((item) => (
          <CardListItem
            key={item.id}
            id={item.id}
            value={item.value}
            onChange={changeValue}
          />
        ))}
      </div>
    </div>
  );
};

export default CardList;

CardListItem.tsx↓

type CardListItemProps = {
  id: number,
  value: string,
  onChange: (id: number, value: string) => void,
};

const CardListItem = ({ id, value, onChange }: CardListItemProps) => {
  return (
    <div className="w-[50%] flex flex-col mb-2 rounded-lg bg-lime-300 p-4">
      <label htmlFor={id.toString()}>id: {id}</label>
      <input
        id={id.toString()}
        type="text"
        value={value}
        onChange={(e) => onChange(id, e.target.value)}
      />
    </div>
  );
};

export default CardListItem;

リファクタ後のコード

変更1:CardListItemコンポーネントをmemo化する

memo化とは再計算を防ぐためにキャッシュ化することで、
メモ化されたコンポーネントは、props が変化しない限り再レンダリングされません。

import { memo } from "react";

type CardListItemProps = {
  id: number,
  value: string,
  onChange: (id: number, value: string) => void,
};

const CardListItem = ({ id, value, onChange }: CardListItemProps) => {
  return (
    <div className="w-[50%] flex flex-col mb-2 rounded-lg bg-lime-300 p-4">
      <label htmlFor={id.toString()}>id: {id}</label>
      <input
        id={id.toString()}
        type="text"
        value={value}
        onChange={(e) => onChange(id, e.target.value)}
      />
    </div>
  );
};

// memo化
export default memo(CardListItem);

これで入力値が変わったCardListItemしか変更しないはずですよね、、
でも実はCardListItemコンポーネントに変更を加えると、
CardListが再描画され、その中で定義しているchangeValue関数も新しく作成され、異なる参照として扱われます。
つまりCardListItemコンポーネントに渡されるpropsのonChangeが変更したこととなり、せっかくmemo化したCardListItemコンポーネントも再描画されてしまいます。

変更2:useCallbackで関数の再描画を防ぐ

useCallback を使うと、依存配列が変化しない限り、同じ関数の参照を再利用します。
CardListItemに渡すonChangeが変わらなくなるということですね。
これでmemoが活きます。

import React, { useCallback, useState } from "react";
import CardListItem from "./CardListItem";

const CardList = () => {
  const [itemValues, setItemValues] = useState([
    { id: 1, value: "" },
    { id: 2, value: "" },
    { id: 3, value: "" },
  ]);

  // useCallback関数内に入れる
  const changeValue = useCallback((id: number, value: string) => {
    setItemValues(
      itemValues.map((item) => {
        if (item.id === id) {
          item.value = value;
        }
        return item;
      })
    );
  }, []);

  return (
    <div>
      <div className="mb-3">itemValues: {JSON.stringify(itemValues)}</div>
      <div className="w-[50%] flex gap-2">
        {itemValues.map((item) => (
          <CardListItem
            key={item.id}
            id={item.id}
            value={item.value}
            onChange={changeValue}
          />
        ))}
      </div>
    </div>
  );
};

export default CardList;

まとめ

  • 繰り返されるコンポーネントはmemo化することを検討しましょう
  • 関数を子供に渡している場合、その関数が不必要に参照が変わらないようuseCallbackで囲みましょう
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?