React useStateフック

概要:このチュートリアルでは、ReactのuseState()フックと、コンポーネントをより堅牢にするために効果的に使用する方法について学びます。

React useState()フック入門

Reactでは、stateは時間とともに変化するデータの一部です。コンポーネントにstate変数を追加するには、useState()フックを使用します。

まず、useStatereactライブラリの関数としてインポートします

import { useState } from 'react';Code language: JavaScript (javascript)

次に、コンポーネント内でuseState()関数を使用します

const [state, setState] = useState(initialValue);Code language: JavaScript (javascript)

useState()関数は、正確に2つの要素を持つ配列を返します

  • 現在のstate値を保持する変数(state)。最初のレンダーでは、state変数はinitialSateの値を持っています。
  • state変数の現在の値を新しい値に変更し、再レンダーをトリガーする関数(setState)。

慣例として、state変数を更新する関数名は、動詞setで始まり、その後に変数名が続きます。

たとえば、state変数がcountの場合、count変数を更新する関数はsetCountです。

const [count, setCount]  = useState(0);Code language: JavaScript (javascript)

useState()関数は、任意の有効なデータ型(文字列、数値、配列、オブジェクトなど)の初期値を受け入れます。

stateの更新

state変数を更新するためにsetState()関数を呼び出すには、2つの方法があります

  • 直接更新。
  • 関数型更新。

直接更新では、前のstateを知る必要はなく、新しい値を直接setState()関数に渡します。例:

setCount(1);Code language: JavaScript (javascript)

この例では、setCount()関数は、現在のstate値に関係なく、新しいcount値を1に設定します。

関数型更新では、前のstateを入力引数として受け取り、新しいstateを返す関数を渡します。例:

setCount(prevCount => prevCount + 1);Code language: JavaScript (javascript)

この例では、setCount()関数は、前のcount値に基づいてcount変数を1ずつインクリメントします。

useStateフックの例

CounterコンポーネントでuseStateフックを使用する基本的な例を見てみましょう

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

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

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h1>Current count: {count}</h1>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default Counter;Code language: JavaScript (javascript)

この例では、useState()フックを使用して、初期値が0のcount変数を宣言しています

const [count, setCount] = useState(0);Code language: JavaScript (javascript)

Incrementボタンがクリックされると、increment()関数が呼び出され、setCount()関数を使用してcount変数を更新します。

count変数が変化するため、Reactはコンポーネントの再レンダーをトリガーし、新しいcount値を表示します。

stateとコンポーネントの再レンダー

Reactでは、setState()関数を呼び出すことによってstateが変化するたびに、コンポーネントが再レンダーされます。コンポーネントの再レンダーを監視するには、次のようにuseEffect()フックを使用できます

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

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

  useEffect(() => {
    console.log('Component rendered or rerendered');
  });

  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h1>Current count: {count}</h1>
      <button onClick={increment}>Increment</button>
    </div>
  );
};

export default Counter;Code language: JavaScript (javascript)

Incrementボタンがクリックされるたびに、count変数が1ずつインクリメントされ、コンポーネントの再レンダーがトリガーされます。コンソールウィンドウには、最初のレンダー時と再レンダーごとに「Component rendered or rerendered」というメッセージが表示されます

新しいstate値が前の値と同じ場合、Reactはコンポーネントを再レンダーしません。

たとえば、setCount()関数を呼び出してもcount変数を変更しない場合、Reactは再レンダーをトリガーしません

const increment = () => {
   setCount(count);
};Code language: JavaScript (javascript)

コンソールウィンドウには、コンポーネントが最初にレンダーされるときに「component rendered or rendered」というメッセージが1つ表示され、Incrementボタンがクリックされたときに同じメッセージは表示されません。

更新直後にcount値をログに記録できます。ただし、ログには更新のcount値が表示されます

const increment = () => {
    // update the count
    setCount(count + 1);

    // show the count before the update
    console.log(count);
};Code language: JavaScript (javascript)

increment()関数では、count値を1ずつインクリメントします

setCount(count + 1);Code language: JavaScript (javascript)

Reactは、コンポーネントをすぐに再レンダーしません。代わりに、更新をスケジュールします。したがって、count変数の値は、更新が有効になる前の現在のcount値です。

Reactは、パフォーマンス上の理由から、複数のstate更新をバッチ処理することに注意することが重要です。そのため、新しいstateがコンソールにすぐに表示されることはありません。

たとえば、setCount()関数を複数回呼び出すことができ、Reactはこれらの更新をバッチ処理して、1回の再レンダーで済みます

const increment = () => {
    // update the count multiple times
    setCount(count + 1);
    setCount(count + 2);
    setCount(count + 3);

    // show the count before the update
    console.log(count);
};Code language: JavaScript (javascript)

この例では、Reactは3つのstate更新をスケジュールし、その結果1回の再レンダーが発生します。コンソールウィンドウには、0、3、6、…のようなcount値が表示されます

その理由は、setCount()が現在のcount値に基づいて3回呼び出されるためです

  • 最初の更新count + 1 – countを1増やします。
  • 2回目の更新count + 1 – 現在のcount(更新されたcountではない)を1増やします。
  • 3回目の更新count + 3 – 現在のcount(最新のcountではない)を1増やします。

現在のcountが0の場合、3回の呼び出しでcountは3に設定されます。現在のcountが3の場合、3回の呼び出しでcountは6に設定されます。

前のstateに基づいてstateを更新するには、前のstateを引数として受け取る関数型更新を使用する必要があります。例:

const increment = () => {
  // update the count multiple times
  setCount((prevCount) => prevCount + 1);
  setCount((prevCount) => prevCount + 2);
  setCount((prevCount) => prevCount + 3);

  // show the count before the update
  console.log(count);
};Code language: JavaScript (javascript)

この例では

  • 最初の更新prevCount + 1 – countを1増やします。
  • 2回目の更新prevCount + 2 – 更新されたcountを2増やします。
  • 3回目の更新prevCount + 3 – 最新のcountを3増やします。

Reactはこれらの更新をバッチ処理し、最終的なstateをprevCount + 6として処理します。そのため、コンソールウィンドウには0、6、12、18などが表示されます。

複雑なstateの処理

コンポーネントに配列またはオブジェクトのstateがある場合は、不変性を確保するために慎重に処理する必要があります。

配列のstateの更新

配列のstateを処理する場合、配列を直接変更することを避けるために、更新時に変更された項目を含む元の配列のコピーを作成する必要があります。

次のFruitListコンポーネントを見てみましょう

import React, { useState } from 'react';

const commonFruits = [
  '🍎 apple',
  '🍌 banana',
  '🍒 cherry',
  '🍈 date',
  '🥭 mango',
  '🍇 grape',
  '🥝 honeydew',
  '🥥 coconut',
  '🍋 kiwi',
  '🍊 lemon',
  '🍉 warter melon',
  '🍊 orange',
  '🍑 peach',
];

const FruitList = () => {
  const [fruits, setFruits] = useState([]);
  const addFruit = () => {
    const newFruit =
      commonFruits[Math.floor(Math.random() * commonFruits.length)];
    setFruits([...fruits, newFruit]);
  };
  return (
    <div>
      <h1>Fruit List</h1>
      <ul>
        {fruits.map((fruit, index) => (
          <li key={index}>{fruit}</li>
        ))}
      </ul>
      <button onClick={addFruit}>Add Fruit</button>
    </div>
  );
};

export default FruitList;
Code language: JavaScript (javascript)

FruitListコンポーネントには、配列であるfruits stateがあります。useStateフックは、それを空の配列に初期化します

const [fruits, setFruits] = useState([]);Code language: JavaScript (javascript)

fruits配列に新しい要素を追加するには、既存のfruitsをコピーし、次の構文を使用して新しいfruitsを追加して、新しい配列を作成します

setFruits([...fruits, newFruit]);Code language: JavaScript (javascript)

配列に項目を追加するためにpush()メソッドを使用すると、配列の項目数が変わっても、同じ配列参照を参照することに注意してください。この場合、Reactはコンポーネントの再レンダーをトリガーしません

// DON'T DO THIS
fruits.push(newFruit);
setFruits(fruits);Code language: JavaScript (javascript)

オブジェクトのstateの更新

配列のstateと同様に、オブジェクトのstateを更新するときは、既存のオブジェクトを変更するのではなく、新しいオブジェクトを作成するようにする必要があります。例:

import React, { useState } from 'react';

const Person = () => {
  const [person, setPerson] = useState({
    name: 'John',
    age: 20,
  });

  const handleIncrement = () => {
    setPerson({ ...person, age: person.age + 1 });
  };

  return (
    <div>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <button onClick={handleIncrement}>Increment Age</button>
    </div>
  );
};

export default Person;Code language: JavaScript (javascript)

この例では、元のオブジェクトの既存のプロパティをコピーし、ageプロパティを更新して、新しいPersonオブジェクトを作成しています

setPerson({ ...person, age: person.age + 1 });

stateの遅延初期化

stateの初期化にコストのかかる計算が必要になる場合があります。パフォーマンスを向上させるために、Reactでは、関数をuseState()に渡すことで、stateを遅延的に初期化できます。useState()は、最初のレンダー中に1回のみこの関数を呼び出します。

次のThemeコンポーネントでは、useState()フックはコンポーネントが再レンダーされるたびにgetTheme()関数を呼び出しますが、これは必要ありません

import React, { useState } from 'react';

function getTheme() {
  console.log('Getting the theme from the local storage');
  return localStorage.getItem('theme') || 'light';
}

const Theme = () => {
  const [theme, setTheme] = useState(getTheme());
  const handleClick = () => {
    console.log('Changing the theme');
    const newTheme = theme === 'light' ? 'dark' : 'light';
    setTheme(newTheme);
    localStorage.setItem('theme', newTheme);
  };
  return (
    <div>
      <p>Current Theme: {theme}</p>
      <button onClick={handleClick}>Swich Theme</button>
    </div>
  );
};

export default Theme;Code language: JavaScript (javascript)

最初の再レンダー中にgetTheme()関数を1回のみ呼び出すようにuseStateフックに指示するには、次のように関数を渡すことができます

const [theme, setTheme] = useState(() => getTheme());Code language: JavaScript (javascript)

この遅延state初期化手法は、すべてのレンダーで繰り返したくない計算を必要とするstateを初期化する必要がある場合、または初期stateにlocalStorageなどの外部ソースからのデータの取得が含まれる場合に使用できます。

概要

  • useState()フックを呼び出して、コンポーネントにstate変数を追加します。
  • useState()関数は、state変数(state)とstateを更新するための関数(setState)の2つの項目の配列を返します。
  • setState関数を使用して、常にstate変数を更新します。stateが変更された場合、setState関数はコンポーネントの再レンダーをトリガーします。
  • 複雑なstateを更新する場合は、配列またはオブジェクトを直接変更するのではなく、新しいコピーを返します。
  • 初期stateの計算にコストがかかる場合は、遅延初期化を使用します。
このチュートリアルは役に立ちましたか?