Articles

ステートマシンを比較する。 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> ); } 

アプリケーションが複雑な場合、新しい機能が追加されると、物事がすぐに制御不能になり、コードの理解、テスト、および拡張が困難になります。 ステート マシンを使用すると、アプリケーションがなり得るすべての状態、状態間の遷移、および発生し得る副作用を定義することができます。

State Diagram

State Diagram

アプリケーションは次の状態になる可能性があります:

  1. ready – アプリケーションが起動するときの初期状態
  2. loading – あるイベントが発生したとき、すなわち、アプリケーションが起動したとき。e a button is clicked
  3. success – when loading resolves
  4. 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 Dashboard Free Trial Banner

LogRocket は Web アプリの DVR のようなもので、React アプリで発生する文字通りすべてのことを記録します。 問題が発生した理由を推測する代わりに、問題が発生したときにアプリケーションがどのような状態であったかを集約して報告することができます。 LogRocket はまた、アプリのパフォーマンスを監視し、クライアント CPU 負荷、クライアント メモリ使用量などのメトリックで報告します。

LogRocket Redux ミドルウェア パッケージは、ユーザー セッションに可視性の追加レイヤーを追加します。 LogRocket は、Redux ストアからすべてのアクションと状態をログに記録します。

React アプリケーションをデバッグする方法を近代化します – 無料で監視を開始します。