はじめに
Next.jsを触り始めて1ヶ月、恥ずかしながら今までuseStateの使い方を理解せずに書いていました・・・
一つアプリを作り終えてからuseStateの使い方を理解したので、アプリを作る前に理解しておけばよかったなという点を超初心者向けに共有します!
筆者の環境
・OS:Windows11
・CPU:Ryzen9 3900X
・エディタ:VScode
・フレームワーク:Next.js(app router/Typescript)
useStateの使い方
useStateとは
詳しくは公式ページを参照していただければと思いますが・・・
ここでは簡単に私の理解を説明します。(正確さより感覚を優先します)
公式の言うところのstate変数についての説明になります。
・要するに変数
・この値が更新されると再レンダリングされる
・値の更新にはset〇〇という関数を使う
・コンポーネント間のデータのやりとりができる
とまぁざっくりですが初心者なりにこんな感じで理解しています。
useStateはどんな時に使うのか?
他の言語から来ると普通の変数ではダメなの?と感じる方もいるかと思います。
普通の変数ではダメな場合、useStateを使うと何がいいのかについて解説します。
仮に画面上に変数の値を表示していた場合、
useStateではない普通の変数が更新されても画面上に表示している値は変更されません。
jQueryなどの場合はDOMの値を書き換えるように指示を明示的に出すことで画面上の値が更新されます。
それと同様のイメージです。
しかし、useStateで宣言した変数の値を更新すると自動的に再レンダリングされ、画面に表示されている値も更新されます。
なので画面上の値を操作と連動して更新したい場合などに利用されます。
また、コンポーネント間で値をやりとりする際にもこのuesStateを使ってやりとりします。
詳しくは後ほど説明します。
ちなみにレンダリングとは・・・
まず、Reactでは画面を表示するために、レンダリングという処理が走ります。
レンダリングとは何をしているのかについてはこちらのサイトなどで詳しく説明しています。
簡単にいうと、今持っている情報で HTMとJavascriptを作成するみたいなイメージです。
一旦仮で作成して、今表示しているDOMと違いがあった場合のみDOMに反映されます。
useStateを使った値の更新
まずはuseStateを使った画面上の値の更新について説明します。
仮に以下のようなプログラムがあったとします。
'use client'
export default function Home() {
let hoge = 2
// ボタンがクリックされたときの処理
const handleClick = () => {
hoge = 3
console.log(hoge)
};
return (
<>
<div>{hoge}</div>
<button className="bg-blue-500 text-white py-1 px-2 rounded" onClick={handleClick}>ボタン</button>
</>
);
}
変数とボタンが画面に表示されており、ボタンを押したら変数の値を更新するプログラムです。
実際の画面は以下のようになります。
このボタンを押すとhogeという変数は3になります。(以下の画像のコンソール参照)
しかし、実際に画面の数字は変わりません。
これは変数が変わっても画面が再レンダリングされないので、表示が変わっていない状態です。
次にuseStateを使用します。
'use client'
import { useState } from 'react';
export default function Home() {
const [hoge, setHoge] = useState(2);
// ボタンがクリックされたときの処理
const handleClick = () => {
setHoge(3)
console.log(hoge)
};
return (
<>
<div>{hoge}</div>
<button className="bg-blue-500 text-white py-1 px-2 rounded" onClick={handleClick}>ボタン</button>
</>
);
}
変更点は、
useStateが使えるようにライブラリを読み込むのと、
import { useState } from 'react';
変数の宣言の仕方(ここでuseStateを使います)
const [hoge, setHoge] = useState(2);
あとは変数の更新の仕方です。
setHoge(3)
useStateを使って変数を設定すると、hogeに変数の実際の値が入って、setHogeにhogeの値をセットする関数が入ります。
そして、実際に値を変更する際にはsetHoge(○)の○に値を入れることで更新できます。
実際にこのプログラムを動かすと、以下のように更新されます。
これはuseStateの値が更新されると再レンダリングされ、画面が再描画されるので画面上でも見た目が変わるということです。
しかし、よく見ていただくと、コンソールに出ている値は更新されていません。
setHogeで値を更新した場合、再レンダリングされたタイミングで変数の値が更新されるようになっているので、このような現象が起きます。
処理の流れは
setHoge → 再レンダリング → hogeの更新
というイメージです。
なので、setHogeの後に処理を入れる場合は値が更新されていない可能性があることに注意してください。
コンポーネント間の値の受け渡し
基本的な使い方がわかったところで、次にコンポーネント間での値の受け渡しについて説明します。
以下のような親コンポーネントと子コンポーネントがあったとします。
親コンポーネント
'use client'
import { useState } from 'react';
import Child from './child';
export default function Home() {
const [hoge, setHoge] = useState(2);
return (
<>
<div>{hoge}</div>
<Child setHoge={setHoge}/>
</>
);
}
子コンポーネント(child.tsx)
import { Dispatch, SetStateAction } from 'react';
export default function Child({setHoge}:{setHoge:Dispatch<SetStateAction<number>>}) {
// ボタンがクリックされたときの処理
const handleClick = () => {
setHoge(3)
};
return (
<button className="bg-blue-500 text-white py-1 px-2 rounded" onClick={handleClick}>ボタン</button>
);
}
先ほどのプログラムからボタンに関する部分を子コンポーネントとしました。
この時、変数の宣言は親コンポーネントで行っていますが、実際に値の更新は子コンポーネントで行っています。
親コンポーネントの以下の部分で変数の更新をする関数を受け渡し、
<Child setHoge={setHoge}/>
子コンポーネントの以下の部分でその変数を受け取っています。
export default function Child({setHoge}:{setHoge:Dispatch<SetStateAction<number>>}) {
このように、set〇〇という関数を受け渡すことで、子コンポーネントで親コンポーネントの値を書き換えることができるようになります。
受け渡したら、あとは元のプログラムと同様に、ボタンを押したら値を更新するという処理を書くのみです。
// ボタンがクリックされたときの処理
const handleClick = () => {
setHoge(3)
};
useStateのデメリット
これまで説明した通り、useStateを使うと値の更新が画面にすぐ反映されるため、大変便利ではあります。
しかし、使うことによるデメリットもあります。
useStateで宣言した変数が更新されると毎回レンダリングが走ります。
そのため、再レンダリングしても画面上何も変わらないのに、useStateで宣言した変数が変わってしまったがために再レンダリングが走ってしまうこともあります。
Reactは親コンポーネントがレンダリングされると、自動的に子コンポーネントもレンダリングされるという特性もあります。
大規模ばプロジェクトになるとこの辺りの動作が処理速度に大きく影響してきてしまいます。
なので、useStateを使う必要がない箇所は極力useStateを使わずに書くことを推奨します。
最後に
以上がuseStateの基本的な使い方についての解説になります。
私は何も理解せずノリと勢いでプログラムを書いてしまったので、useStateを使う必要のない場面でもガンガンuseStateを使ってしまっていました・・・(反省)
上にも書いた通りuseStateを使うと値の更新が画面にすぐ反映されるため、大変便利ではありますが、それと同時に値が更新された時に毎回再レンダリングが走ってしまします。
そのため私が作成したアプリではかなり無駄なレンダリングが発生してしまう仕様になってしまいました。
useStateやuseEffectなどの挙動はしっかり最初に理解してから作成すべきですね・・・
この辺りのレンダリングの最適化についてはこれから勉強していきたいと思います。
何か学びがあれば随時共有できればと思います。
では!
コメント