【React】Reactで破壊的メソッドを使うことは良くない

Reactでは破壊的メソッドの使用は推奨されていません。

### 破壊的メソッドとは

破壊的なメソッドとは、オブジェクトや配列などのデータ構造を変更する操作を指します。これらのメソッドは、元のデータ構造を直接変更し、新しいデータ構造を返さないことが特徴です。そのため、**元のデータが変更される可能性**があります。

`破壊的なメソッド: push()、pop()、shift()、unshift()、splice()など`


### 非破壊的メソッドとは

非破壊的なメソッドは、元のデータ構造を変更せず、新しいデータ構造を返す操作です。**元のデータは変更されず、変更されたコピー**が返されます。

`非破壊的なメソッド: slice()、concat()、map()、filter()など`

### 破壊的メソッドを使ってはいけない理由

Reactはコンポーネントの再レンダリングを行う際に、参照の変更を検知して効率的な更新をいます。
破壊的メソッドが推奨されない理由は、**破壊的なメソッドは元のデータ構造を変更するため、参照が変更されず、Reactが再レンダリングの必要性を検知できなくなるため**です。

### 具体的な例

下記は破壊的メソッドを使用した例です。

```jsx
export default function Sample() {
  const [state, setState] = useState(["a", "b", "c"]);
  const handleButtonClick = () => {
    const newState = state;
    newState.push("d"); // 破壊的なメソッドを使って state を更新するが、state 自体は直接変更されない

    console.log("New state:", newState);
    console.log("Original state:", state);

    setState(newState); // 更新を反映させるが、state は変更されていない
  };

  return (
    <div>
      <button onClick={handleButtonClick}>Add 'd' to state</button>
      <ul>
        {state.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
```
挙動

![2024-03-0916.33.39-ezgif.com-video-to-gif-converter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/f850be6d-4084-f61f-0058-d0db39f2f183.gif)

問題点

- ボタンを押しても"d"が画面上に表示されない



### 更新されない理由とReactの仕組みについて

このコードの問題点は、`const newState = state;`で、`newState`を`state`のコピーではなく、同じ配列を参照している点です。

そのため、console.logで表示されているように、`newState`と`state`、両方で"d"が追加されています。

Reactは、コンポーネントの再レンダリングが必要かどうかを判断する際に、状態の変更を検出します。

`newState`は`state`と同じ配列を参照しているため、実際には`state`自体が直接変更されています。

そのため、`newState`が呼び出されても、`state`の参照が変更されないため、Reactは新しい状態の変更を検出できません。

結果として、状態の更新が反映されず、コンポーネントが再レンダリングされません。

### 修正

修正するためには、新しい配列を作成して、それに変更を加える必要があります。

```jsx
export default function Sample() {
  const [state, setState] = useState(["a", "b", "c"]);
  const handleButtonClick = () => {
    const newState = [...state, 4]; 

    console.log("New state:", newState);
    console.log("Original state:", state);

    setState(newState); // 更新を反映させるが、state は変更されていない
  };

  return (
    <div>
      <button onClick={handleButtonClick}>Add 'd' to state</button>
      <ul>
        {state.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
```

`newState`の作成時に、**スプレッド演算子 ...** を使って`state`の内容を展開しています。これにより、新しい配列が作成され、`state`の内容は変更されません。

`setState`を使って`newState`をセットすることで、Reactに新しい状態を反映させています。

状態を安全に更新し、Reactが再レンダリングをトリガーするために、元の配列を更新するのではなく新しい配列を作成するようにしましょう。

### 破壊的メソッドから非破壊的メソッドへ変換する

A・B・Cのボタンがあります。
初期値は`const array = ["あいうえお","かきくけこ","さしすせそ"];`の文字列が入った配列が格納されています。

`A`のボタンを押した際は、初期値の配列がそのまま表示されます。
`B`のボタンを押した際は、配列の`２番目の値("かきくけこ")`が配列の一番最初に移動します。
`C`のボタンを押した際は、配列の`3番目の値("さしすせそ")`が配列の一番最初に移動します。
他の配列はそのままの順番通りです。

イメージ

![2024-03-0418.14.27-ezgif.com-video-to-gif-converter.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3288047/cc043ebe-107c-9249-8a9a-09b798991016.gif)



### 破壊的なメソッドを使用したNGパターン

```jsx
export default function Sample() {
  const [array, setArray] = useState([
    "あいうえお",
    "かきくけこ",
    "さしすせそ",
  ]);

  const handleButtonClick = (targetName) => {
    const newArray = array.slice(); // 配列をコピーする
    const targetIndex = newArray.indexOf(targetName);
    if (targetIndex !== -1) {
      newArray.splice(targetIndex, 1); // 対象を配列から削除
      newArray.unshift(targetName); // 対象を配列の先頭に追加
      setArray(newArray); // 新しい並び順を設定
    }
  };

  return (
    <>
      <button onClick={() => handleButtonClick("あいうえお")}>A</button>
      <button onClick={() => handleButtonClick("かきくけこ")}>B</button>
      <button onClick={() => handleButtonClick("さしすせそ")}>C</button>
      <ul>
        {array.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </>
  );
}
```

### 非破壊的なメソッドを使用したOKパターン

```jsx
export default function Sample() {
  const [array, setArray] = useState([
    "あいうえお",
    "かきくけこ",
    "さしすせそ",
  ]);

  const handleButtonClick = (targetName: string) => {
    const targetIndex = array.indexOf(targetName);
    if (targetIndex !== -1) {
    // 
    const newArray = [
        targetName,
        ...array.filter((item, index) => index !== targetIndex),
      ];
      setArray(newArray);
    }
  };
  return (
    <>
      <button onClick={() => handleButtonClick("あいうえお")}>Aのボタン</button>
      <button onClick={() => handleButtonClick("かきくけこ")}>Bのボタン</button>
      <button onClick={() => handleButtonClick("さしすせそ")}>Cのボタン</button>
      <ul>
        {array.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </>
  );
}
```


この修正では、`splice()`メソッドと `unshift()`メソッドの代わりに、新しい配列を作成する方法を採用しています。

具体的には、対象の要素を取り除いた新しい配列を`filter()`メソッドを使って作成し、その配列の先頭に対象の要素を追加しています。これにより、元の配列を破壊することなく、状態の更新が行われます。

**スプレッド構文**を使うことでミューターブルではなく、**イミュータブル(変更不可能な値)** なものとして扱われます。

### 最後に

コンポーネントの状態の管理をより安全で予測可能なものになるように、元のデータを直接変更したり、破壊的メソッドを使用することは避けましょう。

### 参考

React ドキュメント 「Local mutation: Your component’s little secret」

https://react.dev/learn/keeping-components-pure#local-mutation-your-components-little-secret

記事

https://qiita.com/takeiin/items/448d76ba1cd65dbc585a

https://zenn.dev/syu/articles/3c4aa813b57b8c

https://zenn.dev/cryptobox/articles/ec99edaaa16b7b