レポートの主な問題は、Reactでの状態管理の難しさです。
たとえば、使い慣れた機能-無限スクロール、つまり、ユーザーがページの最後(またはほぼ最後)までスクロールしたときにデータを読み込む機能を実装しましょう。 この問題を解決する多くの便利なパッケージがありますが、多くの場合、自分で作成する必要があります。
このためにコンポーネントで必要なこと:
-
scroll
イベントのハンドラーを追加します。 - ユーザーがデータをロードする適切な場所までスクロールしたかどうかを確認するチェックを追加します。
- 実際に、データをロードします。
最初の近似ではこれで十分ですが、正しい動作のための要件をさらに追加しましょう。
- 前のバッチがまだロード中の場合は、新しいデータをロードしないでください。
- どういうわけか、データがロードされていることをユーザーに通知します- ロード時またはロード時のような表示。
- すべてがすでにロードされている場合は、データのロードを開始しないでください。
- エラー表示をユーザーに追加します。
私たちの機能が正しく機能するように、データに加えて、状態に保存すべきものを想像してみましょう:
-
isFetching
フラグ-現在データがロードされていることを示します。 - フィールド
error
-error
に関する情報を含める必要があります。 - フィールド
isEmpty
データがないことを示します。 - 突然
retry
機能を追加したい場合、そのための情報を保存する必要があります。
この実装の主な欠点は何ですか:
- 優れたコンテキスト参照。 多くの条件があります。たとえば、適切な場所にスクロールした場合にのみデータをロードしますが、前のデータはロードされません。
- 私たちのコードは読みにくく、理解しにくいです。
- スケーリングするのは困難です-状態に新しいプロパティを追加するとき、すべての条件を調べて、特定の場所で状態を変更してロジックを壊さないようにする方法を理解する必要があります。 また、バグにつながる可能性があります。
ステートマシンは、これらすべての欠点を修正するのに役立ちます。
実際、これは有限状態マシン(Final State Machine)、つまり有限数の状態を含む抽象モデルを使用する原理です。
モデルは、5つのパラメーターを使用して説明されます。
- マシンが配置されるすべての状態。
- マシンが受信したすべての入力データのセット。
- 遷移関数-前の状態と入力データのセットを受け入れ、新しい状態を返します。
- 初期状態。
- 最終状態。
一度にアクティブにできる状態は1つだけです。
したがって、ある状態から別の状態への遷移の条件を決定できます。
例として、信号機の仕事を考えてみましょう。 これは3つの状態を持つマシンであり、それらの順序はわかっています。また、初期状態と最終状態に条件付きで名前を付けることもできます。
反応オートマトンライブラリをコードに追加しましょう-Reactのステートマシンの抽象化です。 これは、さらに別のxstateライブラリ(機能的なステートレス JSステートマシンとステートダイアグラム)のラッパーです。
この理論がこのケースにどのように適用されるかを理解するために、機能がステートチャートのようになる様子を見てみましょう。
はじめに、初期状態、つまりスクロールイベントの追加であるエントリポイントを示します。 さらなる作業の準備ができたら、READYイベントをマシンに送信し、マシンはブート状態に入ります。 ダウンロードが成功し、すべてのデータがまだダウンロードされていない場合、リスニング状態になります。 スクロールするときに、新しいデータをロードするための条件が満たされると、ロード状態に入り、すべてのデータをダウンロードするまでこのサイクルを維持できます。 その後、イベントをリッスンしなくなりました。
概略的に、コードは次のようになります。
import React from "react"; import { hot } from "react-hot-loader"; import { Action, withStatechart } from "react-automata"; export const statechart = { // initial: "attach", // states: { attach: { on: { READY: "fetching" } }, fetching: { on: { SUCCESS: { listening: { // listening SUCCESS //cond - pure , , cond: extState => extState.hasMore }, detach: { cond: extState => !extState.hasMore } }, ERROR: "listening" }, // fetch - , fetching onEntry: "fetch" }, listening: { on: { SCROLL: "fetching" } }, detach: {} } }; class InfiniteScroll extends React.Component { componentDidMount() { // mount fetching this.attach(); } attach() { // fetching //, , - this.element.addEventListener("scroll", this.handleScroll); this.props.transition("READY"); } handleScroll = e => { const { scrollTop, scrollHeight, clientHeight } = e.target; const isCilentAtBottom = 0.9 * (scrollHeight - scrollTop) === clientHeight; if (isCilentAtBottom) { // listening fetching this.props.transition("SCROLL"); } }; fetch() { const { transition } = this.props; loadTodos() .then(res => res.json()) .then(data => transition("SUCCESS", { todos: data })) .catch(() => transition("ERROR")); } render() { // Action - , , return ( <div ref={element => { this.element = element; }} > <Action show="fetch">Loading...</Action> <ul> {this.props.todos.map(todo => <li key={todo.id}>{todo.text}</li>)} </ul> </div> ); } } InfiniteScroll.defaultProps = { todos: [] }; const initialData = { todos: [], devTools: true }; const StateMachine = withStatechart(statechart, initialData)(InfiniteScroll); export default hot(module)(StateMachine);
react-automataからhoc
withStatechart
を使用して初期データを転送すると、マシンの状態を変更するための
transition
メソッドがpropsで利用可能になり、
machineState
がマシンの現在の状態になります。
statechart
変数は、図面のプログラムによる記述です。
アプローチの利点:
- 少ないバグ。
- コードを読みやすく、理解しやすい。
- 何が起こったのか、いつ起こったのかの分離。 最初はコンポーネントによって制御され、2番目は状態図によって制御されます。
便利なリンク:
- React Amsterdam 2018でのMichele Bertoliのレポート: https : //www.youtube.com/watch?v= smBND2pwdUE & t=3137s
- React Automata: https : //github.com/MicheleBertoli/react-automata
- Xstateのドキュメント: http ://davidkpiano.github.io/xstate/docs/#/
- 状態図の説明: http : //www.inf.ed.ac.uk/teaching/courses/seoc/2005_2006/resources/statecharts.pdf