Reactでの状態管理はReactをやっていく上ではもっとも重要となります。
しかし、状態はReactで最も複雑なものであり、初心者と経験豊富な開発者の両方が理解するのに苦労している方々は少なからずいらっしゃいます。
そのため、この記事ではReactの状態のすべての基本について説明します。
Propsの解説も含めますが、状態管理に焦点を当てていきます。
状態を制する者はReactを制すると言っても過言ではありません。
状態の概要
状態により、アプリケーション内の変化するデータを管理できます。
これは、アプリケーションで追跡するさまざまなデータを指定するキーと値のペアを定義するオブジェクトとして定義されます。
Reactでは、作成するすべてのコードはコンポーネント内で定義されます。
Reactでコンポーネントを作成するには、主に2つの方法がございます。
・ クラスベースのコンポーネント
・ 関数型コンポーネント
Reactフックを使用して関数コンポーネントを直接学習するのではなく、まずクラスベースのコンポーネントを理解しておくと、基本を簡単にクリアできる可能性があります。
状態とは?
状態は、Reactコンポーネントクラスを動的にするために使用されます。
これにより、コンポーネントはレンダリング間で変化する情報を追跡できます。
より具体的には、コンポーネントの状態は、コンポーネントの存続期間中に変化する可能性がある情報を保持するオブジェクトとして定義できます。
Reactの状態管理
Reactでは基本的に親コンポーネントが主役です、ですので親コンポーネントでアプリの状態を管理します。
クラスコンポーネントでの状態は、イベントハンドラー、サーバーの応答、またはプロパティの変更に応じて更新できます。
これはsetState()メソッドを使用して行われます。
setStateメソッドは、コンポーネントの状態に対して行われたすべての更新をキューに入れ、更新された状態でコンポーネントとその子を再レンダリングするようにReactに指示します。
状態オブジェクトを変更するには常にsetState()メソッドを使用する必要があります。
コンポーネントが更新されたことを認識し、render()メソッドを呼び出すことが確実になるためです。
まず、状態を使用する場合、コンポーネントの状態が常に存在する必要があるため、初期状態を設定する必要があります。
下記のように、コンポーネントクラスのコンストラクターで状態を定義することで、それを行うことができます。
class MyClass extends React.Component { constructor(props){ super(props); this.state = { attribute : "value" }; } }
下記では、カウンターでの例となります。
クラスコンポーネントがどのように状態を処理するかを示しています。
class App extends React.Component { constructor(props) { super(props); this.state = { counter: 0 }; } render() { return ( <div> <h1>Update Count</h1> <p>count: {this.state.counter}</p> <button onClick={() => this.setState({ counter: this.state.counter + 1 })}>Click me</button> </div> ); } }
クラスコンポーネントでは、状態を初期化する場合、コンストラクタを実装し、super(props)を呼び出す必要があります。
状態オブジェクトはコンストラクターで初期化されます。
変数にアクセスする場合は、thisキーワードでアクセスします。
イベントハンドラーでアロー関数を使用していますので、呼び出す関数内のthisを現在のクラスインスタンスにもバインドされています。
本来であれば、通常は下記のようにインスタンス事にコンストラクタ内にバインドする必要があります。
class App extends React.Component { constructor (props) { super(props) this.state = { counter: 0 } // thisのバインド this.ChangeBtn = this.ChangeBtn.bind(this) } ChangeBtn = () => { this.setState({ counter: this.state.counter + 1 }); } render () { return ( <button onClick={this.ChangeBtn}> Click me </button> ) } }
最後に、状態を設定するには、setStateメソッドを呼び出します、これは状態オブジェクトの値を変更するために使用されます。
関数コンポーネントでは、ほとんどの場合に状態を管理するにはuseStateフックで十分です。
たとえば、単純な文字列またはブール値の状態が必要な場合、このフックは非常に便利で直感的です。
const [state, setState] = useState(false);
useStateフックは、単純で小さな状態を作成し、それを単一のコンポーネント内で管理するときに役立ちます。
const App = () => { const [toggle, setToggle] = useState(false); return ( <div> <h1>toggle is {toggle ? "on" : "off}</h1> <button onClick={() => setToggle(!toggle)}>toggle</button> </div> ) };
先述した、クラスコンポーネントのカウンター機能をそのまま関数コンポーネントとしてリファクタリングした状態管理を見て見ましょう。
const App = () => { const [count, setCount] = useState(0); return ( <div> <h1>Update Count</h1> <p>count: {count}</p> <button onClick={() => setCount(count + 1)}>Click me</button> </div> ); }
関数コンポーネントでは、this のインスタンスがないため、thisのバインドを気にする必要性はありません。
useStateフックを使用する事により、this.setState()とstate = {} を置き換えて、ステートレスから状態を保持するステートフルなコンポーネントとして機能します。
そして、クラスと違い明らかにコードの可読性が向上したことを示しています。
アプリケーションに不要なコードが作成されることはなく、コードの読み取りやデバッグが難しくなることもありません。
useStateフックの解説は当記事で詳しく解説しておりますので下記を参照下さい。
そして、親の状態で保持されたデータはPropsによって子供達へ渡されていきます。
下記は親クラスコンポーネントとなります。
// App.js class App extends React.Component { render() { return ( <div> <h1>親コンポーネントです</h1> <Child title="親から子へ" /> </div> ); } }
下記は子の関数コンポーネントとなります。
// Child.js // 引数にpropsで受けとる const Child = (props) => { // それを変数として代入 const title = props.title return( <div> <h1>子のコンポーネントです<h1> { title } {/* 表示*/} </div> ) }
親と子だけの関係でコンポーネント間でのデータ渡しならPropsを1度渡すだけで問題ありませんが、子から子になるとバケツリレー式状態です。
これは、一方向のデータフローとも呼ばれます。
Propsは一度代入され渡されると子のコンポーネントでは変更する事はできません。
そして値の受け渡しは子から親へは基本的に渡す事はできない事になっています。
それは、常に親コンポーネントによって状態が維持されている為です。
ですがやむを得ずどうしてもやりたい場合は??
親のコンポーネントで変更するコールバック関数を作成しその関数をPropsで子へ渡し、渡された子のコンポーネント内でコールバック関数を呼び出します。
関数をPropsとして渡した場合、その関数は何かしらを返す事が可能です。
子のコンポーネントで親の状態をコントロールする事が可能となります。
そうする事により状態の値が変更可能となります。
Propsについて、詳しく学習されたい方はこちらのReactのprops(小道具)をすべて理解する 渡し方の記事で解説しております。
ですが、そうなった場合は1つ1つの状態管理が大変になってきます。
Reactでの状態管理を行う際に手っ取り早いのはReactのフレームワークであるReduxを使用しての状態管理です。
ReduxにはStoreと言われるのがあり、アプリ内にある状態を全て保管する場所があります。
それの何が良いのか?
つまり、本来の状態管理なら親の下に沢山の子がおりツリー構造になってるのを想像してください。
バケツリレー式で渡されていきます、しかし先程も言ったようにReduxのStoreは全ての状態をグローバルで管理しています。
Stateで管理されたデータの倉庫みたいなものです。
バケツリレー式で渡していかなくてもStoreを間に挟み必要なデータだけを管理していきます。
変更された状態を受け取ったStoreはコンポーネントにその状態を渡しその状態を受け取った、コンポーネントは再描画を行い、ブラウザの表示内容を変更します。
状態の管理が肥大化し複雑になると管理も大変ですがReduxを用いればその問題が解消されます。
Reduxはグローバルな状態を管理するためのReactのフレームワークなのです。
ですが、初心者が使用すると思うと中々難しいかもしれません。
状態管理の変化 フック
今では状態管理はクラスコンポーネントから関数コンポーネントでの状態管理へと移り変わっています。
理由はフック APIの実装により大きく変わりました。
React フックがリリースされる前は、クラスコンポーネントを使用して状態を管理することしかできませんでした。
React フックのリリースにより、Reactコミュニティに新しい時代が始まったのです。
つまり、今まではクラスを用いての状態を保持するコンポーネント作成だったのが、単純にそれを関数でも使用できるようになった、それだけです。
状態などのReact機能をクラスを書かずに使用する事が可能となり、クラス無しでライフサイクルを扱えます。
Propsによるバケツリレーも少なくなり、もちろんクラスを定義するまでの長い記述もしなくて済むのでコード量もコンパクトに見やすくなります。
そして、何よりもクラスでのthisの挙動やbindこの辺りの意識をしなくて済みます。
それがReactの関数型コンポーネントのフック機能です。
クラス状態なしの場合、関数型コンポーネントでは
useStateメソッドを使用し状態を管理します。
クラスを使用しないライフサイクルの場合はuseEffectフックを使用します。
クラスのライフサイクルで頻繁に使用する3つのメソッドそれらをuseEffectで同じ事ができ補えます。
長年に渡り状態管理するならReduxが唯一の選択肢でした、しかしReact v16.3からContext APIが登場しました。
Context APIとは?
Context APIはReactアプリのコンポーネント間でデータのやりとり(共有)する方法です。
小規模なアプリ開発であれば最適なリソースが豊富となっております。
ReduxとContext APIの違いとしては、グローバルで状態管理をするかローカルで状態管理をするかになります。
そして最近では、状態管理ライブラリであるRecoilが登場しました。
状態を管理するライブラリです。
Context APIが抱える制約・問題を解決するためにMetaによって提唱された状態管理ライブラリです。
Reactアプリのすべてのコンポーネントが状態を簡単に共有できボイラープレートコードのセットアップが不要なReduxと比較して最小限となっています。