8
3

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.

useStateを学び直す

Last updated at Posted at 2023-07-18

初めに

ご存知の方も多いと思いますが、React.jsの公式ドキュメントがリニューアルされましたね。
率直な感想は、「めちゃくちゃ分かりやすくなった」です。ポイントは以下の2点です。

  1. ソースコードの追加
    旧版は文章が少なくてソースコードもそこまで多くない印象でしたが、ソースコードが増えて、さらにはクイズが追加されたことでドキュメントというか教材と言えるほど分かりやすくなっていると思います。
  2. 実践的な解説
    実際の開発におけるユースケースに合わせたナレッジが多く追加された印象です。

そのため、基礎から実践まで網羅されているように思います。

改めてドキュメントを読んで、まとめて、言語化しながら学び直したいと思いました。
何回かに分けて記事を執筆したいと思います。

useState

概要

useStateはコンポーネントにstate変数を追加するためのフックスです。
以下のように分割代入の形で使用します。

const [state, setState] = useState(initialState)

それぞれの値の説明は以下の通りです。

  • state
    • 現在のstate
  • setState
    • stateを更新するためのset関数
    • 引数は以下の2通り
      • 更新後のstate
      • 更新用関数
        • 引数に 直前のstateをとり、更新後のstateを返すコールバック関数
  • initialState
    • stateの初期値

引数は基本的に更新後のstateで良いと思います。更新用関数は以下の場合に使うと良いと思います。
①直前のstateから更新後のstateを計算する必要がある
カウンターアプリを作成する場合が分かり良い例かと思います。

const [count, setCount] = useState(0)

// カウントを1増やす処理
const countUp = () => {
    // 直前のstateを参照して1増やす
    setCount(prevCount => prevCount + 1)
}

②useCallbackやuseEffect内の処理でstate変数を使ってstateを更新する
この場合は依存配列にstate変数を設定する必要があります。しかしそうすることで、更新頻度が上がります。それが意図しない挙動であるならset関数で更新用関数を使うと良いです。

const [count, setCount] = useState(0)

// カウントを1増やす処理

const countUp = useCallback(() => {
    // 直前のstateを参照して1増やす
    setCount(count + 1)
}, [count])
⭕️
const countUp = useCallback(() => {
    // 直前のstateを参照して1増やす
    setCount(prevCount => prevCount + 1)
}, [])

気をつけること

以下の5つが挙げられています。1つ目は初学者の方がよくつまづくポイントですね。

  • set関数で処理を行なった後は再レンダーされることでstate変数が更新される
    • 同じスコープ内(関数やuseEffectの中)ではstate変数は変わらない
  • 新しい値が現在のstateと同じ場合は、そのコンポーネントと子コンポーネントの再レンダーはスキップされる
  • stateの更新は、全てのイベントハンドラの実行後にまとめて行われる(バッチ処理)
    • set関数が複数実行されている場合は複数回の再レンダーが発生するわけではない
  • ほとんどないが、前回の値を保持したい場合はレンダー中にset関数を呼び出すことがある

  • Strict Modeでは純粋でない関数を見つけやすくするためにset関数が2回呼ばれる

stateを定義するポイント

UIをインタラクティブにする(ユーザーのアクションに対してシステムが反応してその結果に基づいてユーザーがアクションをとる)ために、DRY(Don't repeat yourself)を守って、stateとして定義するのか、定義しないのかを決める必要があります。

以下の観点からstateとして定義するか判断すると良いです。該当しないものがstateとしてふさわしいということになります。

  • 時間が経っても変わらないものはstateにしない
  • 親から props 経由で渡されるものであればstateにしない
  • コンポーネント内にある既存のstateやpropsに基づいて計算可能なデータであればstateにしない

配列・オブジェクトの更新

基本概念

配列やオブジェクトを更新するときは、以下の2通りであると思いますが、stateは2ではなく1の方法を行う必要があります。

  1. 配列やオブジェクトそれ自体を置き換える
  2. 配列やオブジェクトの要素を書き換える

これは、前提として、stateはイミュータブル(=不変, immutable)である必要があるからです。つまり、stateは「読み取り専用」として扱う必要があります。

ミューテーション

前述の2の方法はミューテーション(mutation)と呼びます。
例えば以下のようなstateがあります。

const [user, setUser] = useState({
 name: 'shogo', 
 age: 30
})

このstateは以下のように書き換えることができます。

usser.age = 31

また、以下のようなstateがあります。

const [itemRankingList, setItemRankingList] = useState(['Apple', 'Banana', 'Orange'])

このstateは以下のように書き換えることができます。

itemRankingList[0] = 'Grapes'

しかし、いずれの場合も再レンダーが行われず、UIをインタラクティブにすることができません。その意味では、ミューテーションは推奨されていないです。

オブジェクトの更新

フィールド単位の更新

既存のオブジェクトの一部を更新したいことが多くあります。その場合は、既存のフィールドをスプレッド構文を使ってコピーしてから更新したいフィールだけを変更します。
前述のstateのageだけを更新したい場合は以下のように記述します。

setUser({
 ...user,
 age: 31,
})

ネストされたオブジェクトの更新

ネストされたオブジェクトとは例えば以下のようなものです。

const [user, setUser] = useState({
    name: 'shogo'
    age: 30,
    address: {
        street: '中央通り',
        city: '東京',
        country: '日本'
    },
})

この場合もスプレッド構文を使ってコピーしてから更新したいフィールドだけを変更します。

setUser({
    ...user
    address: {
        ...user.address,
        street: '西通り',
        city: '大阪'
    },
})

配列の更新

公式ドキュメントに大変分かりやすい表があったので、それを参考に掲載します。

目的 使わない(配列を書き換える) 使う(新しい配列を返す)
追加 push, unshift concat, スプレッド構文
削除 pop, shift, splice filter, slice
要素置換 splice, 代入文 map
ソート reverse, sort 配列をコピーする

「配列を置き換える」メソッドは、破壊的メソッドで対象の配列を変更してしまいます。一方で、「新しい配列を返す」メソッドは、非破壊的メソッドで対象の配列はそのままで、新しい配列を生成することができます。その新しい配列をset関数の引数に指定することでstateを更新するということです。

細かいテクニック

keyを利用してstateをリセットする方法

key属性はリスト(配列)をレンダーする場合に利用しますが、別の使い方もあります。
以下の例は公式ドキュメントにある例です。Formコンポーネントのkey属性にversionが設定されています。「Reset」ボタンをクリックするとversionが更新されますが、Reactはkey属性が変化したときFormコンポーネントを再生成するため、そのstateがリセットされます。

export default function App() {
  const [version, setVersion] = useState(0);

  function handleReset() {
    setVersion(version + 1);
  }

  return (
    <>
      <button onClick={handleReset}>Reset</button>
      <Form key={version} />
    </>
  );
}

リセットするためのトリガーは用意するケースがあると思うので、その時に使いたいと思いました。シンプルに欠けて非常に便利ですね。

直前のレンダーの情報を保存する

あまりないケースであるため割愛しますが、興味がある方は公式ドキュメントを見てください。

まとめ

Reactの流儀はとても参考になる内容で感謝です。
まだまだ足りないところがある気がするので、適宜追記していきたいと思います。
(7/23)useStateについては一通りまとめることができたと思います。他にも覚えておくべきことがあれば、この記事に追記するか、別の記事にまとめていきたいと思います。また、暇な時にReact.jsのソースコードとかも見てみたいですね。

8
3
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
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?