ステートマシンを比較する。 XState vs. Robot
React で状態を管理することは、アプリケーション ロジックがますます複雑になるにつれて面倒になることがあります。 Redux、Flux、および MobX などのサードパーティ製ライブラリが役立ちますが、これらのツールにも独自のオーバーヘッドがあります。
ステート マシンは、有限状態マシンまたは有限状態オートマトンとも呼ばれ、計算の数学モデルです。 このガイドでは、XState と Robot という 2 つのステート マシンの類似点、相違点、長所、短所を確認し、React アプリケーションで状態管理を簡略化するためにそれらを使用する方法について説明します。 API からデータを取得するコンポーネントを考えてみましょう。
const Todo = () => { const = useState(); const handleClick = () => { fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(todo => setData(data.push(todo)) .catch(error => console.error(error) ) } return( <div> <button onClick={handleClick}> Fetch Data </button> {data && data.map(todo => (<p key={todo.id}> {todo.title} <span> {todo.completed} </span></p>) )} </div> ); }
この例では、イベント (この場合はボタンのクリック) が発生するたびに変化するアプリケーションの部分であるため、データが私たちの状態です。
ユーザーがレコードを取得するのを待っている間、または取得中にエラーが発生した場合はどうなるでしょうか。 これらの問題を処理するために、さらに状態を追加する必要があります。
const Todo = () => { const = useState(false); const = useState(); const = useState(false); const handleClick = () => { setLoading(true); fetch('https://jsonplaceholder.typicode.com/todos/1') .then(response => response.json()) .then(todo => { setLoading(false); setData(data.push(todo)); }) .catch(error => { setLoading(false); setIsError(true); }) } return( <div> {loading && <p> Loading Data... </p>} <button onClick={handleClick}> Fetch Data </button> {data && data.map(todo => (<p key={todo.id}> {todo.title} <span> {todo.completed} </span></p>) )} {error && <p> An error occured. Try again.</p>} </div> ); }
アプリケーションが複雑な場合、新しい機能が追加されると、物事がすぐに制御不能になり、コードの理解、テスト、および拡張が困難になります。 ステート マシンを使用すると、アプリケーションがなり得るすべての状態、状態間の遷移、および発生し得る副作用を定義することができます。
アプリケーションは次の状態になる可能性があります:
-
ready
– アプリケーションが起動するときの初期状態 -
loading
– あるイベントが発生したとき、すなわち、アプリケーションが起動したとき。e a button is clicked -
success
– when loading resolves -
error
– when loading is rejected
the app transitions from one state to another when an action is triggered – i.e……, アクションが発生したとき、つまり、ユーザーがボタンをクリックしたときに、アプリはある状態から別の状態に遷移します。 XState と Robot は何をするのか。
公式ドキュメントによると、XState は有限状態マシンおよびステート チャートを作成、解釈、実行するためのライブラリであり、またアクターとしてそれらのマシンの起動を管理するためのライブラリでもあります。
Robot は Mathew Philips によって作成された軽量で機能的、そして不変の有限ステートマシンを構築するためのライブラリです。 XState、Statecharts、および P プログラミング言語に触発されました。
前提条件
To follow along with this tutorial, you’ll need:
- Knowledge of JavaScript
- Knowledge of React
- yarn or npm v5.2以上
- Node version 10以上
はじめに
XStateとRobotの類似点と相違点を示すために、APIからデータを取得するアプリケーションを作成することにします。
ターミナルを開き、Reactアプリケーションを初期化します。
npx create-react-app state-machine
これでState MachineというReactアプリケーションが作成されました。
次に、APIからデータを取得するサービスを作成します。
cd src && touch fetchTodo.js
上記のコマンドでsrc
ディレクトリにfetchTodo.js
というファイルを作成します。
ファイルを開いて以下を入力します。
export const fetchTodo = () => { return fetch('https://jsonplaceholder.typicode.com/todos/1') .then((response) => response.json()) .then((todo) => todo);};
基本的に、fetchTodo
関数が呼ばれたときはいつでも、APIから取得したデータを返します。
インストール
XStateは、npmやyarnを使ってインストールするか、CDNを通じてスクリプトを埋め込むことが可能です。
npm を使用してライブラリをインストールするには、ターミナルを開いて次のように実行します:
npm install xstate @xstate/react
これにより、xstate
コア ライブラリと、React アプリケーションでカスタム XState フックを使用できる @xstate/react
という React 用パッケージがインストールされます。
Robotはnpmやyarnを使ってインストールできますが、CDNは使えません。
Robotをインストールするには、ターミナルを起動し、次のコマンドを実行します。
npm install robot3 react-robot
Robotは、react-robot
というReactでカスタムフックを使用するためのパッケージも提供しています。
マシンを作成する
ステートマシンを使用する前に、まずそれを定義しなければなりません。
src
ディレクトリに、xstateMachine.js
というファイルを作成します。 作成したファイルに以下のコードをコピーします。
import { Machine, assign } from 'xstate';import { fetchTodo } from '../fetchTodo';export const xstateMachine = Machine({ id: 'clickButton', initial: 'ready', context: { todo: null, }, states: { ready: { on: { CLICK: 'loading', }, }, loading: { invoke: { id: 'fetch-todo', src: fetchTodo, onDone: { target: 'success', actions: assign({ todo: (context, event) => event.data, }), }, onError: 'error', }, }, success: { on: { CLICK: 'loading', }, }, error: { on: { CLICK: 'loading', }, }, },});
マシンはMachine()
ファクトリー関数を使用して定義します。 上のコードで定義したマシンは、ID、状態、コンテキスト、アクション、遷移で構成されています。 IDはステートノードを識別するために使用されます。
マシン内のステートは次のとおりです:
ready
loading
success
error
コンテキストは数値や任意の文字列、オブジェクトなどの定量データを表現するために使用する拡張ステートです。 アプリケーションの初期状態はready
と定義される。 ボタンがクリックされると遷移が起こり、状態がready
からloading
に移ります。
loading
状態では、invoke
プロパティがあり、約束を解決するか拒否するかのどちらかを担当します。 fetchTodo
プロミスが解決されるたびにloading
状態はsuccess
状態に遷移し、assign
アクションはプロミスから得た結果でコンテキストを更新する。
Robotでマシンを作成する場合も同様ですが、いくつかの重要な相違点があります。
あなたのsrc
ディレクトリにrobotMachine.js
というファイルを作成し、以下を貼り付けてください。
import { createMachine, invoke, reduce, state, transition } from 'robot3';import { fetchTodo } from '../fetchTodo';const context = () => ({ todo: {},});export const robotMachine = createMachine( { ready: state(transition('CLICK', 'loading')), loading: invoke( fetchTodo, transition( 'done', 'success', reduce((ctx, evt) => ({ ...ctx, todo: evt.data })) ), transition( 'error', 'error', reduce((ctx, ev) => ({ ...ctx, error: ev.error })) ) ), success: state(transition('CLICK', 'loading')), error: state(transition('CLICK', 'loading')), }, context);
Robotでは、マシンはcreateMachine
関数を使って作成します。
ある状態から別の状態への移動はtransition
関数で行われ、イベントと次の状態をパラメータとして受け取ります。 オプションとして、reduce
関数を3番目のパラメータとしてtransition
に追加することができる。 Reduce 関数は、reducer 関数をパラメータとして受け取り、コンテキストを更新するために使用されます。
Robot にも invoke
関数があり、XState の invoke
プロパティに似ています。 アプリケーションがloading
ステートにあるとき、invoke
関数が呼び出されます。 invoke
関数は、プロミスを呼び出して関数や別のマシンを返す状態の一種である。 invoke
関数が約束を解決すると、done
イベントが送信される。 拒否された場合は、error
イベントを送信します。
コンポーネントの構築
マシンの準備ができたので、次のステップはマシンを利用するコンポーネントを構築します。
コンポーネント用に src
ディレクトリにファイルを作成し、次の内容を貼り付けます。
import React from 'react';import { useMachine } from '@xstate/react';import { xstateMachine } from './stateMachine';function Todo() { const = useMachine(xstateMachine); const { todo } = current.context; return ( <div> <button onClick={() => send('CLICK')}>Fetch Todo XState</button> {current.matches('loading') && <p>loading...</p>} {current.matches('success') && ( <p key={todo.id}> {todo.title} <span> {todo.completed} </span> </p> )} {current.matches('error') && <p>An error occured</p>} </div> );}export default Todo;
マシンを使用するには、先に作成したマシンと同様に、@xstate/react library
からuseMachine
フックをインポートする必要があります。
フックは、マシンを解釈する React Hook です。 コンポーネントのライフサイクルを通じて実行されるサービスを開始する役割を果たします。
useMachine
フックは、パラメーターとしてマシンを受け取り、配列を返します。 この配列には current
state と send
が含まれ、これは useMachine
フックが生成したサービスにイベントを送る関数である。
current
state はステート、コンテキスト、およびいくつかのユーティリティ関数からなるオブジェクトである。 現在の状態を確認するには、ブール値を返すmatches
プロパティを使用する。 ユーザがボタンをクリックすると、サービスにイベントが送られます。 その後、マシンの現在の状態をチェックし、状態に基づいて適切な UI をレンダリングします。
コンポーネントを構築する Robot のアプローチも似ています。 Robot で構築されたコンポーネントは次のようになります。
import React from 'react';import { useMachine } from 'react-robot';import { robotMachine } from './robotMachine';function Todo() { const = useMachine(robotMachine); const { todo } = current.context; return ( <div> <button onClick={() => send('CLICK')}>Fetch Todo Robot</button> {current.name === 'loading' && <p>loading...</p>} {current.name === 'success' && ( <p key={todo.id}> {todo.title} <span> {todo.completed} </span> </p> )} {current.name === 'error' && <p>An error occured</p>} </div> );}export default RobotTodo;
Robot にも useMachine
フックがあり、react-robot library
をインポートしてアクセスすることが可能です。 実装の違いは、状態の比較の仕方にあります。 XState が比較しようとする文字列を受け取る関数である matches
プロパティを使用するのに対し、Robot は name
プロパティを使用して比較前に現在の状態をチェックします。
結論
ステート マシンは React アプリケーションで状態を管理するはるかに優れた組織的方法を提供し、他の代替手段と比較して簡単に拡張できます。 XState と Robot は、非常によく似たライブラリですが、非常に異なる観点からマシンの処理にアプローチします。
このチュートリアルのリポジトリは GitHub で利用可能です。
- Robot documentation
- XState documentation
- “Infinitely Better UIs with Finite Automata” (video)
Full visibility into production React apps
特にユーザーが再現しにくい問題を経験したとき、React アプリケーションのデバッグは難しくなることがあります。 Redux の状態の監視と追跡、JavaScript エラーの自動表面化、遅いネットワーク リクエストとコンポーネントのロード時間の追跡に興味がある場合は、LogRocket を試してみてください。
LogRocket は Web アプリの DVR のようなもので、React アプリで発生する文字通りすべてのことを記録します。 問題が発生した理由を推測する代わりに、問題が発生したときにアプリケーションがどのような状態であったかを集約して報告することができます。 LogRocket はまた、アプリのパフォーマンスを監視し、クライアント CPU 負荷、クライアント メモリ使用量などのメトリックで報告します。
LogRocket Redux ミドルウェア パッケージは、ユーザー セッションに可視性の追加レイヤーを追加します。 LogRocket は、Redux ストアからすべてのアクションと状態をログに記録します。
React アプリケーションをデバッグする方法を近代化します – 無料で監視を開始します。