Reactで最も一般的に使用されるフックの1つはuseStateです。
使用頻度も、ダントツで高いです。
ここではReact 18を使用した簡単な例で解説していきます。
初心者が一般的な問題を解決する方法について疑問が生じることは間違いありません。
それらを一緒に解決していきましょう。
このフックの使用方法と実装方法を学び終えると、それは本当にシンプルで使いやすく、便利なフックである事が分かってきます。
学ぶ前の注意点
※ この記事はReactフックuseStateの初心者向けになりますのでご了承下さい。
※ useStateとuseEffectフックを使用した解説もございます。
useStateとは
Reactでは、関数コンポーネントは基本的にステートレスコンポーネントであり、その状態を管理することはできません。
React 16.8バージョンでのフックの導入により、関数コンポーネントを作成し、それに状態を追加する必要がある場合は、useStateフックを使用できます。
useStateは、ローカル状態を追加するために関数コンポーネント内で呼び出す必要があるフックです。Reactは、コンポーネントの再レンダリング間でこの状態を保持します。
つまりuseStateフックを使用すると、JSXの関数コンポーネントに状態変数を含めることができます。
名前付きエクスポートで使用する際は波括弧で囲ってインポートします。
import { useState } from 'react'
または、デフォルトエクスポートでインポートせずに使用可能です。
const [] = React.useState()
名前付きインポートで読み込むのは一般的なので、なるべく前者のアプローチを使用下さい。
useState()が返す値は、2つの値を持つ配列で構成され、useStateの引数には初期値を渡します。
下記の初期値は( 0 )
となっております。
const [state, setState] = useState(0)
まずは配列内が1つの場合で表示してみましょう。
const App = () => { const [string] = useState("Hello,"); return ( <div> <h1>{string}welcome.</h1> { //出力: Hello,welcome } </div> ); };
配列内が1つの場合では、 string
という名前が変数の役目をしている事が分かるかと思います。
ですが、本来の使用目的は配列内が2つの場合がほとんどです。
配列内が1つの場合は、Reactコンポーネントの状態を強制的に再レンダリングする場合で使用したりします。
useState基本的な状態操作
それでは、先述のおさらいをしますと、useState()が返す値は、2 つの値を持つ配列で構成されます。
変数と関数となります。
const App = () => { const [string, setString] = useState("更新前") return ( <div> {string} <button onClick={() => setString("状態が更新されました")}>Click me</button> </div> </div> ) }
DEMOを展開
最初の値は状態変数の初期値または開始値で、2番目の値はその変数の値を更新するために使用できる関数への参照となっております。
配列で両方の値を一度に代入して、コンポーネントで使用できるようにすることができます。
つまり、最初の値の文字列(初期値)はstring
に格納されています、2番目の関数はsetString
になり、その関数は値を返す関数または新しい状態を受け取る関数なので状態を更新する為にあります。
または、インラインではなくJSX外部で関数を宣言しても、同じ動作です。
const App = () => { const [string, setString] = useState("更新前") const handleChange = () => setString("状態が更新されました") return ( <div> {string} <button onClick={handleChange}>Click me</button> </div> </div> ) }
JSX外部での関数では、onClick
に渡された関数の名前の後に括弧( )
がない事に注意して下さい。
そしてuseStateフックは、クラスコンポーネントのsetStateメソッド同様に非同期です。
Reactの状態を更新するプロセスはパフォーマンス上の理由から非同期となっております。
したがって、状態の更新が要求された場合、更新がすぐに行われるという保証はありませんので覚えておいてください。
下記でのuseStateフックはすべて有効な状態データ型となります。
const [mystring, setString] = useState("文字列") const [array, setArray] = useState( [配列] ) const [mynumber, setNumber] = useState(0)//数値 const [myboolean, setBoolean] = useState(true or false) //真偽値 boolean const [myobject, setObject] = useState({ name : "", mail : "" }) //オブジェクト const [func, setFunc] = useState( () => { //関数を渡す })
useState() は1つの状態変数を宣言するためにのみ使用できることに注意しなければなりません。
useStateフックを複数で状態宣言
複数の状態変数を処理する事がある場合は、複数のuseStateを呼び出して使用し、必要な数の状態を宣言が可能です。
下記でのそれぞれの各状態は独立しており、対応する状態更新機能がある事になります。
//複数の状態変数を宣言します const App = () => { const [string, setString] = useState("Hello") const [num, setNum] = useState(10) const [count , setCount] = useState(0); }
useStateが引数なしで呼び出された場合は、初期状態値として呼び出されたかのように動作します。
これはつまり、useStateでの下記2つの呼び出しは同等となります。
useState(); // undefined useState(undefined);
引数無しの初期状態の値はundefined
になります。
以前のReactはundefined
だと、実行時にエラーをスローします。
そのエラーを回避する際は、null
を返す必要がありましたが、React v18ではコンポーネントで未定義でもレンダリングできます。
Suspenseに変更が加えられた事により、エラーをスローする代わりに、undefined
またはnull
のフォールバックをレンダリングできるようになりました。
useStateフックを使用する際は、必ず何かしらの初期値を渡すようにして下さい。
つまり、渡す初期値がない場合は基本的に空を渡してあげます。
useState( [ ] ); useState(""); useState({ });
でないと引数無しのuseStateは未定義となるので、思い通りに機能しない可能性があります。
これは初心者がよく引き起こすエラーの1つです。
useStateフックで数値の状態更新
数値を1ずつカウントアップする単純な例でuseStateフックでの数値の状態更新の操作を学びましょう。
const App = () => { const [count, setCount] = useState(0) const handleClick = () => setCount(count + 1) return ( <div> カウント: {count} <div> <button onClick={handleClick}>count up</button> </div> </div> ) }
See the Pen React useState 数値の状態操作 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
上記であれば、初期値の( 0 )
は1つ目のcount
変数となり、ボタンがクリックされ状態を更新するのは2つ目の関数であるsetCount
となっています。
最後に解説しますが、下記のような事に注意して下さい。
// ✖︎ const handleClick = () => count = count + 1 // ◯ const handleClick = () => setCount(count + 1)
これは、フックルール
です状態を直接更新をしてはいけない事になっておりReactによって厳守されたルールとなっています。
useStateフックで真偽値の状態操作
useStateフックでもっとも単純な、true/false
の真偽値で表示と非表示で切り替え操作をしましょう。
import { useState } from "react"; const App = () => { //初期値はfalse const [boolean, myBoolean] = useState(false); const handleClick = () => { myBoolean(!boolean); } return ( <div className="#"> <div> <p>Copyright© 2022</p> </div> <br /> <button onClick={handleClick}>続きを読む•••</button> {boolean && ( <p> This sentence is a dummy.(この文章はダミーです) Nequeporroquisquamest qui dolorem ipsum quia dolor sit amet、consectetur、adipiscivelit... </p> )} </div> ); };
See the Pen React useState 真偽値の状態操作 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
boolean
は初期値を格納する変数で、myBoolean
は値を返す関数または新しい状態を受け取る関数でもあります。
boolean
では段落の表示と非表示です、初期値はfalse設定にします。
そしてboolean
の初期値状態を更新する為のmyBoolean
関数で論理否定で初期値を反転させ現在の状態を更新します。
useStateフックで配列の状態操作
デフォルトで配列を用意し作成できます。
const testArray = ['Apple', 'Banana', 'Peach']; const [myArray, setMyArray] = useState(testArray);
また配列から中央の配列要素を取得する場合は、slice()
メソッドを使用します。
最初の引数は、開始インデックスで2番目の引数は終了位置のインデックスとなります。
const testArray = ['Apple', 'Banana', 'Peach', 'Grape']; const [myArray, setMyArray] = React.useState(testArray); const arr = testArray.slice(1, 3); console.log(arr)// Banana, Peach
指定の際はインデックス番号なので気をつけて下さい。
初期値として空の配列を作成が可能です。
const [array, setArray] = useState([]) const onClick = () => { setArray((arr) => [...arr, 'Array']); }
useStateフックで配列要素の追加
初期値を空の配列として、ボタンクリックで要素を追加する方法です。
useStateは配列を使用する場合は、クラスコンポーネントのsetStateメソッドとは異なる動作をします。
JSに精通してる方であれば、要素の追加はpush()
でしょ?って思うかもしれませんが、Reactでこれは許可されておりません。
ですので、変わりとして既存の値を追加するための回避策を見つける必要があるので、関数内部でES6の spread演算子(…)
の構文を使用します。
特定のIDと値を持つ新しい変数を作成し、それを既存の配列に追加します。
const [array, MyArray] = useState([]); MyArray( newVariables => [...newVariables, '新しい値']);
つまり、その値はspread演算子を使用して関数にコピーされます。
const App = () => { const [array, MyArray] = useState(['Apple', 'Peach', 'Grape']); const onClick = () => { MyArray( array => [...array, 'Banana']); }; return ( <div> <input type="button" onClick={ onClick } value="Update" /> <div> <div>{ array }</div> </div> </div> ) }
See the Pen React useState 状態の更新 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
上記では、更新された値を持つ新しい配列をコピーして返す事になります。
更新関数は変更をオーバーライドします。
つまり、既存の要素を新しい配列にコピーし、最後に新しい要素を追加します。
ですが、1度に複数の種類のデータを扱う必要がある場合では最適なのはオブジェクトを扱う事です。
他の例を見てみましょう。
下記では追加と削除となります。
import { useState } from "react"; const App = () => { const [list, setList] = useState([]); const handleAdd = () => { const arr = list; setList([...arr, `リスト${arr.length}`]); }; const handleRemove = () => { const arr = list; if (arr.length > 0) { const removeIndex = arr.length - 1; setList(arr.filter((item, index) => index !== removeIndex)); } }; return ( <div> <div> リストの合計数: {list.length} </div> <ul> {list.map((item, index) => <li key={index}>{item}</li>)} </ul> <button onClick={handleAdd}>追加</button> <button onClick={handleRemove}>削除</button> </div> ) }
See the Pen React useState 配列の追加 spread( ... )構文 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
JavaScriptで配列に新しい要素を追加するために通常使用されるメソッドではpush()
ではなく、フックとSpread演算子
を使用しReactで配列の状態を更新する方法と言う事になります。
使用した事がない方は、この機会に覚えるようにして下さい。
配列だけでなく、オブジェクトでもReactでは頻繁に使用します。
useStateフックでオブジェクトの状態操作
文字列や数値とは対照的に、useStateに渡される初期値としてオブジェクトを使用することもできます。
import { useState } from "react"; const App = () => { const [obj, setObj] = useState({ name: "", email: "", age: "" }); const handleChange = e => { e.persist(); setObj(prevUser => ({ ...prevUser, [e.target.name]: e.target.value })); } return ( <div className="App"> <h2>useStateフック オブジェクト</h2> <br /> <p>Name:</p> <input type="text" name="name" value={obj.name} onChange={handleChange} placeholder="入力してください"/> <br /><br /> <p>Email:</p> <input type="text" name="email" value={obj.email} onChange={handleChange} placeholder="入力してください"/> <br /><br /> <p>Age:</p> <input type="text" name="age" value={obj.age} onChange={handleChange} placeholder="入力してください"/> <br /><br /> <label>出力:</label> <pre>{JSON.stringify(obj, null, 2)}</pre> </div> ); }
See the Pen React useState 配列の追加と削除 by dev.K | Webアプリ開発者 (@enjinia_f) on CodePen.
onChange
各入力要素にイベントハンドラーを追加し、ユーザーは入力フィールドから値を変更するときにその値を確認できます。
入力フィールドに変更を加えたときに関数がトリガーされるようにします。
そして、obj
という名前のオブジェクトに変更を加えたときに、オブジェクトの現在の状態を表示できます。
オブジェクトからkeyを削除
const App = () => { const [obj, setObj] = React.useState({ name: "", email: "", age: "" }); const handleChange = e => { e.persist(); setObj(prevUser => ({ ...prevUser, [e.target.name]: e.target.value })); } const removeKey = () => { setObj(current => { // remove name key from object const {name, ...rest} = current; return rest; }); }; return( <div> {// 省略…} <button onClick={removeKey}>Remove</button> </div> ) }
React v17またはそれ以前のバージョンの場合だとボタンクリック後にエラーが発生します、下記のDEMOで確認ができます。
DEMOを展開
または下記で、実際の出力を確認できます。
React v18で学習されてる方は、エラーがスローされる事はありません。
それは初期値がundefined
またはnull
でもエラーがスローされる事なくレンダリングされる為です。
以前でのバージョンの方でエラーが返された場合はオブジェクトフィールドを新しく再定義するか短絡評価をしないとエラーが返されます。
それは初期値がnull
またはundefined
の場合、Reactではそのコンポーネントは uncontrolled
とみなされます。
つまり(非制御)制御されていないコンポーネントと解釈されます。
簡単に説明しますと、ReactDOMによって状態が管理されているのかまたは、Reactの外部リアルDOMによって管理されたコンポーネントなのかです。
ReactDOMによって管理されたコンポーネントは制御されたコンポーネントと呼ばれます。
つまり、状態は常にReact状態によって管理しなければいけません。
これらについて詳しく学ばれたい方は下記を参照下さい。
アイテムを削除したい場合は、value
の値に短絡評価を使用した方が手っ取り早いかと思います。
ボタンクリックをすると、エラーが返る事なくオブジェクトからname
は削除されます。
<input value={obj.name || ""} type="text"/>
DEMOを展開
また、React v18同様にdelete
演算子を使用する事も可能です。
const removeKey = () => { setObj(current => { const copy = {…current}; delete copy['name']; return copy; }); };
delete
演算子を使用する場合は、スプレッド構文(...)を使用し、状態オブジェクトのコピーを作成するようにしてください。
useStateでのコールバック
ReactのuseStateフックを使用してる場合、フックに渡すことができるのは初期状態のみであるため、コールバック関数が欠落している可能性があります。
それは、コールバックをuseState関数の2番目の引数として渡そうとすると、useStateではコールバックをサポートしていないのでエラーがスローされます。
ですのでコールバックを呼び出さないでください。
useStateフックでコールバックを実現する方法は、基本的にはuseEffectフックを使用します。
useEffectフックに精通がない方は下記で解説しておりますので参照下さい。
このまま学ばれたい方に、少し解説致します。
useEffectフックは2つの値を受け入れます
最初の引数は、コンポーネントが画面にレンダリングされるとすぐに呼び出される関数となります。
2番目の引数は、最初の引数である関数が呼び出されたすべての依存関係を保持する配列依存関係で構成されています。
ライフサイクルの流れですと下記のようになります。
useEffect(() => { // Mounting //デフォルトですべてのレンダリング後に実行されるコールバック return () => { // Cleanup function //コンポーネントがアンマウントされたときに呼び出されるようになります。 } }, [//Updating]) // 第2引数 //渡された依存関係が変更度に繰り返し実行されます。
また、第2引数である依存関係配列が空の場合、関数はコンポーネントが最初にDOMにマウントされたときにのみ実行されます。
useEffect(() => { // Mounting return () => { // Cleanup function } }, [ ]) // 第2引数が空 //フックが再び実行されないように無限ループの問題を解決します。
useStateフックでコールバックする簡単な例は下記となります。
const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log("count value", count); }, [count]); //依存配列に入れる return ( <div className="App"> <button onClick={() => setCount((e) => e + 1)}>count up</button> <p>{count}</p> </div> ); }
カウントの状態が変化したときに実行されるコールバックを持つuseEffectフックを追加します。
つまり、初回のレンダリングではコールバックを呼び出しません。
状態を変更した場合にのみ呼び出します。
そしてsetCount
でコールバックを渡します。
これは、コールバックが状態の変更だけでなく、特定の状態の更新にも関連することを意味しており、状態が変化するたびにコールバックをトリガーするのではなく、特定の状態でのみトリガーしたい場合です。
上記ではuseEffectフックを使用し、状態の変化をログで見ることができます。
useStateフックで状態の初期化
関数から状態を遅延初期化することも可能です。
useStateに関数を指定すると、初回のレンダリングでのみ実行されます。
計算コストが高いまたは複雑な計算の関数は、遅延して初期化できます。
const App = () => { const [state, setState] = useState(() => myNumber()) function myNumber() { return Math.floor(Math.random() * 1044) } console.log(state) return (<div></div> ) }
useStateフックは関数を実行しますが、初回レンダリングでのみ初期状態を取得します。
コンポーネントを再レンダリングすると、関数は再度実行される事はありません。
useSateフックを使用する際の注意点
最後にuseStateを使用する際のいくつかの注意点があります。
特に初心者の観点から、掘り下げて紹介します。
まず、useStateフックは関数コンポーネントの最上部と内部に配置する必要があります。
// ✖︎ const App = () => { const onClick = () => { if(true){ const [count, setCount] = useState() } } // ◯ const App = () => { //最上位に配置 const [count, setCount] = useState() const onClick = () => { if(true){ setCount(count + 1) } }
下記では、先述で解説した通りです状態は直接更新をしないで下さい。
// ✖︎ const App = () =>{ const [count, setCount] = useState(0) count = count + 1 } // ◯ const App = () => { const [count, setCount] = useState(0) setCount(count + 1) }
ネストされた関数内で誤って呼び出す
function test1() { let on = false; let setOn = () => {}; function test2() { // ✖︎ [on, setOn] = useState(false); } } // ◯ function test1(x) { const [on, setOn] = useState(false); } function test2() { // ... } }
これは初心者がよく間違えるuseStateの呼び出しです、一般的な問題に注意する必要があります。
useStateフックの1ランク上はuseReducer
(レデューサー)フックとなります。
useReducerフックは主にオブジェクトや配列を管理する際に使用され、大規模なプロジェクトで複雑な状態の遷移に必要となります。
下記で基礎を解説しております。
最後に
useStateフックは、コンポーネントの状態、そしてアプリケーション全体の状態を管理するのに非常に役立ちます。大きなリファクタリングする必要なく、少量のコードで状態管理が簡単になります。
そして状態変数として機能した関数コンポーネントはストートレスからステートフルコンポーネントにすることができます。
useStateフックとは何か、およびそれがどのように機能するかを理解していただければ幸いです。
Reactの初心者に役立つことを願っております。
本日は以上となります。
最後までこの記事を読んで頂きありがとうございます。
この記事が気に入ったら、ブックマークし他の方に共有してください。👏