34
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

さっくり解説するReact hooksのuseState

Last updated at Posted at 2022-09-19

そもそもフック(hook)とは?

フックはReact16.8で追加された機能であり、
これによりstateなどのReactの機能を、クラスを書かずに使えるようになりました。

useStateとは?

useStateは関数コンポーネントにてstateを保持・更新するための機能です。
初回のレンダリングで、初期値となる値をuseStateに引数として渡し、その値が1つ目の要素のstateとなります。
2つ目の要素はstate値を更新するための関数となり、基本的にこの関数を使ってstate値を更新します。

以下のように定義します。

import React, { useState } from 'react';

const Example = () => {
  const [state, stateを更新する関数] = useState(初期値);

  ~~~~

useStateを試してみる

Example.tsx
import React, { useState } from 'react';

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

  return (
    <div>
      <p>ボタンが {count} 回押されました</p>
      <button onClick={() => setCount(count + 1)}>
        ボタン
      </button>
    </div>
  )
};

countというstateを宣言しています。
ボタンが押されると、stateの更新する関数であるsetCountを呼び出し、
countの値に1をプラスします。
これによりボタンが押されるたびにcountが増えていきます。

image.png

stateを更新する関数はstate名の頭にsetをつけるという命名が一般的ですが、
必ずその命名にする必要はありません。

ちなみに、stateの更新する関数は関数を渡すことも可能です。

Example.tsx
import React, { useState } from 'react';

export const Example = () => {

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

  const handleOnClick = () => {
    setCount(updateCount);
  }

  return (
    <div>
      <p>countの値は{count} です</p>
      <button onClick={handleOnClick}>
        ボタン
      </button>
    </div>
  )
};

const updateCount = (count: number) => count + 1

このように更新ロジックをコンポーネント外に切り出すことができるので
覚えておいて損はないかと思います。

useStateで気を付けること

stateが作成されるのはコンポーネントの初回レンダリング時だけ

再レンダリングされても再度stateが作成されるわけではありません。

必ずトップレベルで呼び出す

ループや条件分岐・ネストされた関数内などで呼び出してはいけません。
これによって、コンポーネントがレンダリングされる際のフックが呼ばれる順番を保証します。

関数コンポーネント、もしくはカスタムフック内で呼び出す

ちなみにカスタムフックとは、React hooksの処理をコンポーネントに直接書かずに、別ファイルに切り出して新しいhooksとして定義した関数のことです。

stateの値を更新するときは、必ず更新関数を使う

例えば以下のような処理があるとします。

Example.tsx
import React, { useState } from 'react';

export const Example = () => {
  const [count, setCount] = useState(["a", "b", "c"]);

  const handleOnClick = () => {
    count.push("d");
    setCount(count); 

    console.log(count); // -> (4) ['a', 'b', 'c', 'd']
  }

  return (
    <div>
      <p>countの値は{count} です</p>
      <button onClick={handleOnClick}>
        ボタン
      </button>
    </div>
  )
};

実行結果

image.png

コンソール

(4) ['a', 'b', 'c', 'd']

ボタンが押された場合、handleOnClickが実行されてcountに文字列の「d」を追加していますが、
ボタンをいくら押しても画面に表示されているcountの値は変わりません。

count.push("d");
setCount(count); 

ここで更新の関数を使っているのになぜ変わらないのかというと、
stateの変更の判定にObject.is()が使用されているからです。

そのため同じ配列のstateを更新したところで、
変更が検知されずに再レンダリングも行われないということです。

ではどうすれば良いかというと、スプレッド構文で新たに配列を作成してあげて、
更新関数に渡してあげます。
配列の更新は、基本的にこれが推奨されているようです。

Example.tsx
import React, { useState } from 'react';

export const Example = () => {
  const [count, setCount] = useState(["a", "b", "c"]);

  const handleOnClick = () => {
    setCount([...count, "d"]);
  }

  return (
    <div>
      <p>countの値は{count} です</p>
      <button onClick={handleOnClick}>
        ボタン
      </button>
    </div>
  )
};

実行結果

image.png

これらのルールが守らないと、stateの保持が正しく行われなくなります。

useStateで更新したstateはすぐに更新されない

先ほどの処理にconsole.logを仕込んでみます。

Example.tsx
import React, { useState } from 'react';

export const Example = () => {
  const [count, setCount] = useState(["a", "b", "c"]);

  const handleOnClick = () => {
    setCount([...count, "d"]);
    console.log(count); // -> (3) ['a', 'b', 'c']
  }

  return (
    <div>
      <p>countの値は{count} です</p>
      <button onClick={handleOnClick}>
        ボタン
      </button>
    </div>
  )
};

コンソール

(3) ['a', 'b', 'c']

これは更新関数の呼び出しが非同期なので、
次のレンダリングされるタイミングまでstateが反映されないということです。

そんなときはuseEffectを使うと良いかと思います。

countが変更されたときにuseEffectが実行され、
この時にレンダリングもされるので、コンソールにもcount更新後の値を表示することができました。

Example.tsx
import React, { useEffect, useState } from 'react';

export const Example = () => {
  const [count, setCount] = useState(["a", "b", "c"]);

  useEffect(() => {
    console.log(count) // -> (4) ['a', 'b', 'c', 'd']
  }, [count])

  const handleOnClick = () => {
    setCount([...count, "d"]);
  }

  return (
    <div>
      <p>countの値は{count} です</p>
      <button onClick={handleOnClick}>
        ボタン
      </button>
    </div>
  )
};

image.png

まとめ

更新関数の挙動や、stateが即時反映されないなど、誰しもが詰まったことがあるのではないでしょうか?
useStateは簡単に見えて実はかなり奥が深いですね。

参考

34
32
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
34
32

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?