LoginSignup
0
0

[自分用]ニ◯・ロビン = 破壊的メソッド

Posted at

useStateで配列を使っていく

初期値の内容に応じて配列を作る。空配列でもOK!!

export default function Home() {
  const [array, setArray] = useState([1,2,3]);

  return (
    <>
      <button onClick={handleAdd}>追加</button>
      <ul>
        {array.map((item) => {
          return <li key={item}>{item}</li>;
        })}
      </ul>
    </>
  )
}

ちなみに上記のコードでは[1]・[2]・[3]の三つが配列に加わり、<li>が三つ追加される。

export default function Home() {
  const [array, setArray] = useState([]);
  
  const handleAdd = useCallback(() => {
    setArray((array) => {
      const newArray = [...array,1]
      return newArray;
    });
  },[])

  return (
    <>
      <button onClick={handleAdd}>追加</button>
      <ul>
        {array.map((item) => {
          return <li key={item}>{item}</li>;
        })}
      </ul>
    </>
  )
}

上記のコードでは「追加」を押したら、配列に「1」が追加されていく。

setArray((array) => {
  const newArray = [...array,1]
  return newArray;
});

ここの部分を解説。
setArrayの中が関数になっているので、前回の配列情報を使える。
「追加」がクリックされるとその時の配列を認識(arrayのこと)し、「1」を追加した新しい配列をnewArrayに格納する。(破壊的であるpushを非破壊的に記述している。)
そして仕上げにnewArrayreturnで返す。
※key(1)の被りでエラーが出ると思うが、実際の実装ではデータベースのID等を使うと思うので一旦無視

入力したテキストを配列に格納していきたい

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

export default function Home() {
  const [text , setText] = useState("");
  const [array, setArray] = useState([1,2,3]);

  {/* ここのtextに注目 */}
  const handleAdd = useCallback(() => {
    setArray((array) => {
      const newArray = [...array,text]
      return newArray;
    });
  },[text])

  return (
    <>
      <input type="text" value={text} onChange={e => {
        setText(e.target.value);
      }} />
      <button onClick={handleAdd}>追加</button>
      <ul>
        {array.map((item) => {
          return <li key={item}>{item}</li>;
        })}
      </ul>
    </>
  )
}

handleAdd関数部分の解説。
textはuseStateを使ったインプットタグの入力値。

引数のarrayで前回の配列を受け取り、その配列にtextを追加する。変更点は追加されたtextだけなので、新たにtextだけが追加される。

注意点は第二引数に[text]を書き忘れないこと!
何故ならコールバック関数を使っているため、textを変更したところでhandleAddは再レンダリングされない。一番初めに[1,2,3]を取得し、そのままになってしまう。

ちなみにsome(配列の中の少なくとも1つの要素が当てはまるかどうかを判定し、trueかfalseを返す)を使うと、二重登録を防ぐ事もできる!
return arrayを記述しないとその下の処理が実行されて、結局追加されてしまうので忘れないように!

const handleAdd = useCallback(() => {
  setArray((array) => {
    if (array.some((item) => item === text)) {
      alert("既に存在しています");
      return array;
    }
    const newArray = [...array,text]
    return newArray;
  });
},[text])

(非)破壊的メソッドとは

破壊的メソッドとは、元のデータ(今回だと配列)が変更されることをいう。
そのため変更前の配列を使用するために変数を指定しても、思っている結果と違うことがある。

それに比べて非破壊的メソッドとは、新しい配列を用意して作業することで、元のデータを保持することができる。

そこで役に立つのがスプレッド構文なのだが、改めて復習したいと思う。

あらためてスプレッド構文のおさらい

const arr = ['一つ目', '二つ目'];

console.log(arr);//['一つ目', '二つ目']
console.log(...arr);//一つ目 二つ目

arrをそのまま出力すると配列をそのまま出力され、...arr(スプレッド構文)を出力すると配列の中身が個別で出力される。
それを応用すると、要素を簡単に引数に入れることができる

onst arr = [1, 2, 3, 4, 5];

const newArr = (num1, num2, num3, num4, num5) => {
  return num1 + num2 + num3 + num4 + num5;
};

console.log(newArr(arr[0], arr[1], arr[2], arr[3], arr[4]));//15
console.log(newArr(...arr));//15

同じ15が返るのにconsole.log(newArr(...arr))の方が圧倒的に楽!

そしてここからが重要

配列をコピーしたい!!!

//下記の配列を変数arr5に代入したい!
const arr = [1, 2, 3, 4, 5];

const arr5 = arr;
console.log(arr5);//[1, 2, 3, 4, 5]

無事arr5の中身が[1, 2, 3, 4, 5]になったのでめでたしめでたし!!ではない。
その理由が下記コード。

arr5[0] = 800;
console.log(arr);//[800, 2, 3, 4, 5]
console.log(arr5);//[800, 2, 3, 4, 5]

あれ?arr5しか変更してないのに、arrまで変わってしまっている、、、

(てかReactって状態が変更されないと再レンダリングされないじゃん?元の配列も一緒に変わったんなら、一生状態に変更がないから再レンダリングされないよね・・・。おもしろ・・・。
元の状態と差異を出すためにも、非破壊的メソッドを使って再レンダリングさせるのか。)

そうつまり配列のコピーは「ニ○・ロビン」とようなものなのだ。
たくさん分身は出せるけど、そのダメージは何故か自分にかえってくる。

ということでニ○・ロビン = 非破壊的メソッドという式が成り立つ。

スプレッド構文を使って非破壊的メソッドにする

const arr = [1, 2, 3, 4, 5];

//ここを少しかえる
const arr5 = [...arr];

arr5[0] = 800;
console.log(arr);//[1, 2, 3, 4, 5]
console.log(arr5);//[800, 2, 3, 4, 5]

よし!これで本当にめでたしめでたし!!
何故こうなったのかというと、const arr5 = [...arr]このように代入することでコピーではなく新しい配列を作ることができるからだ。
つまりニ○・ロビンのように見えてニ○・ロビンではないということだ。

なぜ破壊的メソッドがダメなのか

これを理解するためには「ミュータブル(変更可能)・イミュータブル(変更不可能)」について知る必要がある。
簡単にいうと一度値を作成した後に、その値を変更できるかどうかということ。

文字列や数値はデフォルトでイミュータブルになっている。
要は変数に何はアクションを与えたときに、参照元の変数(元の変数)に影響を与えないということ。

let str = "Hello";
str.concat(" World"); // 新しい文字列 "Hello World" が生成されるが、str の値は変わらない

let num = 10;
num + 5; // 新しい数値 15 が生成されるが、num の値は変わらない

それに比べて配列とオブジェクトはデフォルトでミュータブルになっている。
(参考コードは「あらためてスプレッド構文のおさらい」にあるよ)

そのため配列とオブジェクトもイミュータブルとして扱うべく、破壊的メソッドを避けるような記述をするようになった。

0
0
3

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