Redux、RxJS、およびReduxを使用したリアクティブアプリの構築-ReactNativeで観察可能

公開: 2022-03-11

リッチでパワフルなウェブアプリとモバイルアプリの成長するエコシステムでは、現在のユーザー、読み込まれたアイテムのリスト、読み込み状態、エラーなど、管理する状態がますます増えています。 Reduxは、状態をグローバルオブジェクトに保持することにより、この問題を解決する1つの方法です。

Reduxの制限の1つは、すぐに使用できる非同期動作をサポートしていないことです。 このための1つの解決策は、JavaScriptのリアクティブプログラミング用の強力なライブラリであるRxJSに基づくredux-observableです。 RxJSは、Microsoftで開発されたリアクティブプログラミング用のAPIであるReactiveXの実装です。 ReactiveXは、リアクティブパラダイム、関数型プログラミング、オブザーバーパターン、およびイテレーターパターンの最も強力な機能のいくつかを組み合わせたものです。

このチュートリアルでは、ReduxとReactでの使用法について学習します。 また、RxJSを使用したリアクティブプログラミングと、それによって面倒で複雑な非同期作業を非常に簡単にする方法についても説明します。

最後に、RxJSを利用して非同期作業を行うライブラリであるredux-observableを学習し、Reduxとredux-observableを使用してReactNativeでアプリケーションを構築します。

戻ってきた

GitHubで説明されているように、Reduxは「JavaScriptアプリの予測可能な状態コンテナー」です。 JavaScriptアプリにグローバルな状態を提供し、状態とアクションをReactコンポーネントから遠ざけます。

Reduxを使用しない一般的なReactアプリケーションでは、プロパティまたはpropsを介してルートノードから子にデータを渡す必要があります。 このデータフローは小さなアプリケーションでは管理できますが、アプリケーションが大きくなるにつれて非常に複雑になる可能性があります。 Reduxを使用すると、コンポーネントを互いに独立させることができるため、信頼できる唯一の情報源として使用できます。

Reduxはreact-reduxを使用してReactで使用できます。これは、ReactコンポーネントがReduxからデータを読み取り、アクションをディスパッチしてReduxの状態を更新するためのバインディングを提供します。

戻ってきた

Reduxは、次の3つの簡単な原則として説明できます。

1.信頼できる唯一の情報源

アプリケーション全体の状態は、単一のオブジェクトに保存されます。 Reduxのこのオブジェクトはストアによって保持されています。 Reduxアプリには1つのストアが必要です。

 » console.log(store.getState()) « { user: {...}, todos: {...} }

ReactコンポーネントのReduxからデータを読み取るには、 react-reduxconnect関数を使用します。 connectは4つの引数を取りますが、それらはすべてオプションです。 今のところ、 mapStateToPropsと呼ばれる最初のものに焦点を当てます。

 /* UserTile.js */ import { connect } from 'react-redux'; class UserTile extends React.Component { render() { return <p>{ this.props.user.name }</p> } } function mapStateToProps(state) { return { user: state.user } } export default connect(mapStateToProps)(UserTile)

上記の例では、 mapStateToPropsは最初の引数としてグローバルRedux状態を受け取り、親コンポーネントによって<UserTile />に渡されたpropsとマージされるオブジェクトを返します。

2.状態は読み取り専用です

Reduxの状態はReactコンポーネントでは読み取り専用であり、状態を変更する唯一の方法はアクションを発行することです。 アクションは、状態を変更する意図を表すプレーンオブジェクトです。 すべてのアクションオブジェクトにはtypeフィールドが必要であり、値は文字列である必要があります。 それ以外は、アクションの内容は完全にあなた次第ですが、ほとんどのアプリは、アクションの構造を4つのキーのみに制限するflux-standard-action形式に従います。

  1. typeアクションの任意の文字列識別子。 すべてのアクションには固有のアクションが必要です。
  2. payload任意のアクションのオプションのデータ。 いつでも使用でき、アクションに関する情報が含まれています。
  3. errorアクションがエラーを表す場合にtrueに設定されたオプションのブールプロパティ。 これは、拒否されたPromise. string アクションのPromise. string識別子。 すべてのアクションには固有のアクションが必要です。 慣例により、 errortrue場合、 payloadはエラーオブジェクトである必要があります。
  4. meta Metaは、任意のタイプの値にすることができます。 これは、ペイロードの一部ではない追加情報を対象としています。

アクションの2つの例を次に示します。

 store.dispatch({ type: 'GET_USER', payload: '21', }); store.dispatch({ type: 'GET_USER_SUCCESS', payload: { user: { id: '21', name: 'Foo' } } });

3.純粋関数で状態が変化する

グローバルRedux状態は、レデューサーと呼ばれる純粋関数を使用して変更されます。 レデューサーは前の状態とアクションを取り、次の状態を返します。 レデューサーは、既存の状態オブジェクトを変更する代わりに、新しい状態オブジェクトを作成します。 アプリのサイズに応じて、Reduxストアには単一のレデューサーまたは複数のレデューサーを含めることができます。

 /* store.js */ import { combineReducers, createStore } from 'redux' function user(state = {}, action) { switch (action.type) { case 'GET_USER_SUCCESS': return action.payload.user default: return state } } function todos(state = [], action) { switch (action.type) { case 'ADD_TODO_SUCCESS': return [ ...state, { id: uuid(), // a random uuid generator function text: action.text, completed: false } ] case 'COMPLETE_TODO_SUCCESS': return state.map(todo => { if (todo.id === action.id) { return { ...todo, completed: true } } return todo }) default: return state } } const rootReducer = combineReducers({ user, todos }) const store = createStore(rootReducer)

状態からの読み取りと同様に、 connect機能を使用してアクションをディスパッチできます。

 /* UserProfile.js */ class Profile extends React.Component { handleSave(user) { this.props.updateUser(user); } } function mapDispatchToProps(dispatch) { return ({ updateUser: (user) => dispatch({ type: 'GET_USER_SUCCESS', user, }), }) } export default connect(mapStateToProps, mapDispatchToProps)(Profile);

RxJS

RxJS

リアクティブプログラミング

リアクティブプログラミングは、「ストリーム」内のデータの流れとその伝播および変更を処理する宣言型プログラミングパラダイムです。 JavaScriptのリアクティブプログラミング用のライブラリであるRxJSには、オブザーバブルの概念があります。これは、オブザーバーがサブスクライブできるデータのストリームであり、このオブザーバーには時間の経過とともにデータが配信されます。

オブザーバブルのオブザーバブルは、 nexterrorcompleteの3つの関数を持つオブジェクトです。 これらの機能はすべてオプションです。

 observable.subscribe({ next: value => console.log(`Value is ${value}`), error: err => console.log(err), complete: () => console.log(`Completed`), })

.subscribe関数は、オブジェクトの代わりに3つの関数を持つこともできます。

 observable.subscribe( value => console.log(`Value is ${value}`), err => console.log(err), () => console.log(`Completed`) )

オブザーバブルのオブジェクトを作成し、サブスクライバー、別名オブザーバobservableを受け取る関数を渡すことで、新しいオブザーバブルを作成できます。 サブスクライバーには、 nexterrorcompleteの3つのメソッドがあります。 サブスクライバーは、必要な回数だけ値を指定してnextを呼び出し、最後にcompleteまたはerrorを発生させることができます。 completeまたはerrorを呼び出した後、observableは値をストリームにプッシュしません。

 import { Observable } from 'rxjs' const observable$ = new Observable(function subscribe(subscriber) { const intervalId = setInterval(() => { subscriber.next('hi'); subscriber.complete() clearInterval(intervalId); }, 1000); }); observable$.subscribe( value => console.log(`Value is ${value}`), err => console.log(err) )

上記の例では、1000ミリ秒後にValue is hiされます。

毎回手動でオブザーバブルを作成することは、冗長で退屈になる可能性があります。 したがって、RxJSには、オブザーバブルを作成するための多くの関数があります。 最も一般的に使用されるもののいくつかof 、、 from 、およびajaxです。

ofは一連の値を受け取り、それをストリームに変換します。

 import { of } from 'rxjs' of(1, 2, 3, 'Hello', 'World').subscribe(value => console.log(value)) // 1 2 3 Hello World

から

fromほとんどすべてのものを値のストリームに変換します。

 import { from } from 'rxjs' from([1, 2, 3]).subscribe(console.log) // 1 2 3 from(new Promise.resolve('Hello World')).subscribe(console.log) // 'Hello World' from(fibonacciGenerator).subscribe(console.log) // 1 1 2 3 5 8 13 21 ...

ajax

ajaxは文字列URLを受け取るか、HTTPリクエストを行うオブザーバブルを作成します。 ajaxには関数ajax.getJSONがあり、 ajax()によって返される他のプロパティなしでAJAX呼び出しからネストされた応答オブジェクトのみを返します。

 import { ajax } from 'rxjs/ajax' ajax('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log) // {request, response: {userId, id, title, completed}, responseType, status} ajax.getJSON('https://jsonplaceholder.typicode.com/todos/1').subscribe(console.log) // {userId, id, title, completed} ajax({ url, method, headers, body }).subscribe(console.log) // {...}

オブザーバブルを作成する方法は他にもたくさんあります(完全なリストはここで確認できます)。

演算子

オペレーターはRxJSの真のパワーハウスであり、必要なほとんどすべてのオペレーターがあります。 RxJS 6以降、演算子はオブザーバブルオブジェクトのメソッドではなく、 .pipeメソッドを使用してオブザーバブルに適用される純粋関数です。

地図

mapは単一の引数関数を取り、ストリーム内の各要素に射影を適用します。

 import { of } from 'rxjs' import { map } from 'rxjs/operators' of(1, 2, 3, 4, 5).pipe( map(i=> i * 2) ).subscribe(console.log) // 2, 4, 6, 8, 10 

地図

フィルター

filterは単一の引数を取り、指定された関数に対してfalseを返す値をストリームから削除します。

 import { of } from 'rxjs' import { map, filter } from 'rxjs/operators' of(1, 2, 3, 4, 5).pipe( map(i => i * i), filter(i => i % 2 === 0) ).subscribe(console.log) // 4, 16 

フィルター

flatMap

flatMap演算子は、スチーム内のすべてのアイテムを別のストリームにマップし、これらのストリームのすべての値をフラット化する関数を取ります。

 import { of } from 'rxjs' import { ajax } from 'rxjs/ajax' import { flatMap } from 'rxjs/operators' of(1, 2, 3).pipe( flatMap(page => ajax.toJSON(`https://example.com/blog?size=2&page=${page}`)), ).subscribe(console.log) // [ { blog 1 }, { blog 2 }, { blog 3 }, { blog 4 }, { blog 5 }, { blog 6 } ] 

FlatMap

マージ

mergeは、2つのストリームからのアイテムを到着順にマージします。

 import { interval, merge } from 'rxjs' import { pipe, take, mapTo } from 'rxjs/operators' merge( interval(150).pipe(take(5), mapTo('A')), interval(250).pipe(take(5), mapTo('B')) ).subscribe(console.log) // ABAABAABBB 

マージ

演算子の完全なリストは、こちらから入手できます。

Redux-観察可能

Redux-観察可能

設計上、Reduxのすべてのアクションは同期しています。 Redux-observableは、監視可能なストリームを使用して非同期作業を実行し、その非同期作業の結果を使用してReduxで別のアクションをディスパッチするReduxのミドルウェアです。

Redux-observableはEpicsのアイデアに基づいています。 エピックは、アクションのストリーム、およびオプションで状態のストリームを取得し、アクションのストリームを返す関数です。

関数(action $:監視可能、state $:StateObservable ):観測可能;

慣例により、ストリーム(_aka _observable )であるすべての変数は$で終わります。 redux-observableを使用する前に、ストアにミドルウェアとして追加する必要があります。 エピックはオブザーバブルのストリームであり、このスチームを出るすべてのアクションはパイプでストリームに戻されるため、同じアクションを返すと無限ループになります。

 const epic = action$ => action$.pipe( filter(action => action.type === 'FOO'), mapTo({ type: 'BAR' }) // not changing the type of action returned // will also result in an infinite loop ) // or import { ofType } from 'redux-observable' const epic = action$ => action$.pipe( ofType('FOO'), mapTo({ type: BAZ' }) )

このリアクティブアーキテクチャは、すべてのパイプの出力が、それ自体を含むすべてのパイプと、Reduxのレデューサーにフィードバックされるパイプのシステムと考えてください。 何が入り、何がブロックされるかを決定するのは、これらのパイプの上部にあるフィルターです。

ピンポンの叙事詩がどのように機能するか見てみましょう。 pingを受け取り、サーバーに送信します。リクエストが完了した後、pongをアプリに送り返します。

 const pingEpic = action$ => action$.pipe( ofType('PING'), flatMap(action => ajax('https://example.com/pinger')), mapTo({ type: 'PONG' }) ) Now, we are going to update our original todo store by adding epics and retrieving users. import { combineReducers, createStore } from 'redux' import { ofType, combineEpics, createEpicMiddleware } from 'redux-observable'; import { map, flatMap } from 'rxjs/operators' import { ajax } from 'rxjs/ajax' // ... /* user and todos reducers defined as above */ const rootReducer = combineReducers({ user, todos }) const epicMiddleware = createEpicMiddleware(); const userEpic = action$ => action$.pipe( ofType('GET_USER'), flatMap(() => ajax.getJSON('https://foo.bar.com/get-user')), map(user => ({ type: 'GET_USER_SUCCESS', payload: user })) ) const addTodoEpic = action$ => action$.pipe( ofType('ADD_TODO'), flatMap(action => ajax({ url: 'https://foo.bar.com/add-todo', method: 'POST', body: { text: action.payload } })), map(data => data.response), map(todo => ({ type: 'ADD_TODO_SUCCESS', payload: todo })) ) const completeTodoEpic = action$ => action$.pipe( ofType('COMPLETE_TODO'), flatMap(action => ajax({ url: 'https://foo.bar.com/complete-todo', method: 'POST', body: { id: action.payload } })), map(data => data.response), map(todo => ({ type: 'COMPLEE_TODO_SUCCESS', payload: todo })) ) const rootEpic = combineEpics(userEpic, addTodoEpic, completeTodoEpic) const store = createStore(rootReducer, applyMiddleware(epicMiddleware)) epicMiddleware.run(rootEpic);

_重要:エピックは、RxJSの他の監視可能なストリームとまったく同じです。 それらは、完全な状態またはエラー状態になる可能性があります。 この状態になると、エピックとアプリは機能しなくなります。 したがって、蒸気の潜在的なエラーをすべてキャッチする必要があります。 これには__catchError__演算子を使用できます。 詳細: redux-observableでのエラー処理

リアクティブTodoアプリ

UIを追加すると、(最小限の)デモアプリは次のようになります。

リアクティブTodoアプリ
このアプリのソースコードはGithubで入手できます。 Expoでプロジェクトを試すか、Expoアプリで上記のQRコードをスキャンしてください。

要約されたReact、Redux、およびRxJSチュートリアル

リアクティブアプリとは何かを学びました。 また、Redux、RxJS、およびredux-observableについて学び、ReactNativeを使用してExpoでリアクティブなTodoアプリを作成しました。 ReactおよびReactNative開発者にとって、現在の傾向はいくつかの非常に強力な状態管理オプションを提供します。

繰り返しになりますが、このアプリのソースコードはGitHubにあります。 以下のコメントで、リアクティブアプリの状態管理についての考えを自由に共有してください。