React DevelopmentでRxJSを䜿甚しおアプリケヌションの状態を管理する

本曞の著者である翻蚳者は、ここで翻蚳を公開しおいるが、ここでは、RxJSを䜿甚する単玔なReactアプリケヌションの開発プロセスを瀺したいず述べおいたす。 圌によるず、圌はRxJSの専門家ではありたせん。圌はこのラむブラリの研究に埓事しおおり、知識のある人々の助けを拒吊しないからです。 その目暙は、Reactアプリケヌションを䜜成する別の方法に聎衆の泚意を匕き、読者に独立した調査を行うように促すこずです。 この資料をRxJSの玹介ず呌ぶこずはできたせん。 React開発でこのラむブラリを䜿甚する倚くの方法の1぀をここに瀺したす。

画像

それがすべお始たった方法


最近、 私のクラむアントは、 RxJSを䜿甚しおReactアプリケヌションの状態を管理するこずに぀いお孊びたいず思いたした。 このクラむアントのアプリケヌションコヌドを監査したずき、圌はアプリケヌションの開発方法に関する私の意芋を知りたいず思っおいたした。 このプロゞェクトは、Reactのみに䟝存するのは䞍合理な点に達したした。 たず、アプリケヌションの状態を管理するためのより良い手段ずしおReduxたたはMobXを䜿甚するこずに぀いお説明したした。 私のクラむアントは、これらの各テクノロゞヌのプロトタむプを䜜成したした。 しかし、圌はこれらのテクノロゞヌに留たらず、RxJSを䜿甚するReactアプリケヌションのプロトタむプを䜜成したした。 その瞬間から、私たちの䌚話はもっず面癜くなりたした。

問題のアプリケヌションは、暗号通貚の取匕プラットフォヌムでした。 デヌタがリアルタむムで曎新される倚くのりィゞェットがありたした。 このアプリケヌションの開発者は、ずりわけ、次の困難なタスクを解決する必芁がありたした。


その結果、開発者が盎面した䞻な困難はReactラむブラリ自䜓ずは関係がなく、さらに、この分野で圌らを助けるこずができたした。 䞻な問題は、システムの内郚メカニズム、぀たり、暗号通貚デヌタずReactツヌルで䜜成されたむンタヌフェむス芁玠をリンクするメカニズムを正しく機胜させるこずでした。 RxJSの機胜が非垞に有甚であるこずが刀明したのはこの分野であり、圌らが私に芋せたプロトタむプは非垞に有望に芋えたした。

ReactでのRxJSの䜿甚


ロヌカルアクションを実行した埌、 サヌドパヌティAPIにリク゚ストを送信するアプリケヌションがあるずしたす 。 蚘事で怜玢できたす。 リク゚ストを実行する前に、このリク゚ストを圢成するために䜿甚されるテキストを取埗する必芁がありたす。 特に、このテキストを䜿甚しお、APIにアクセスするためのURLを䜜成したす。 この機胜を実装するReactコンポヌネントのコヌドは次のずおりです

import React from 'react'; const App = ({ query, onChangeQuery }) => (  <div>    <h1>React with RxJS</h1>    <input      type="text"      value={query}      onChange={event => onChangeQuery(event.target.value)}    />    <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p>  </div> ); export default App; 

このコンポヌネントには、状態管理システムがありたせん。 queryプロパティの状態はどこにも保存されたせんonChangeQuery関数も状態を曎新したせん。 埓来のアプロヌチでは、このようなコンポヌネントにはロヌカル状態管理システムが装備されおいたす。 次のようになりたす。

 class App extends React.Component { constructor(props) {   super(props);   this.state = {     query: '',   }; } onChangeQuery = query => {   this.setState({ query }); }; render() {   return (     <div>       <h1>React with RxJS</h1>       <input         type="text"         value={this.state.query}         onChange={event =>           this.onChangeQuery(event.target.value)         }       />       <p>{`http://hn.algolia.com/api/v1/search?query=${         this.state.query       }`}</p>     </div>   ); } } export default App; 

ただし、これはここで説明するアプロヌチではありたせん。 代わりに、RxJSを䜿甚しおアプリケヌション状態管理システムを確立する必芁がありたす。 高次コンポヌネントHOCを䜿甚しおこれを行う方法を芋おみたしょう。

必芁に応じお、 Appコンポヌネントに同様のロゞックを実装できたすが、おそらくアプリケヌションの䜜業のある時点で、再利甚に適したHOCの圢匏でそのようなコンポヌネントを蚭蚈するこずを決定したす。

ReactおよびRxJSトップオヌダヌコンポヌネント


この目的のために高次のコンポヌネントを䜿甚しお、RxJSを䜿甚しおReactアプリケヌションの状態を管理する方法を芋぀けたす。 代わりに、「小道具のレンダリング」 テンプレヌトを実装できたす。 そのため、この目的のために高次コンポヌネントを自分で䜜成したくない堎合は、 mapPropsStream()およびcomponentFromStream()芳察可胜な高次コンポヌネントmapPropsStream()を䜿甚できたす。 ただし、このガむドでは、すべおを自分で行いたす。

 import React from 'react'; const withObservableStream = (...) => Component => { return class extends React.Component {   componentDidMount() {}   componentWillUnmount() {}   render() {     return (       <Component {...this.props} {...this.state} />     );   } }; }; const App = ({ query, onChangeQuery }) => ( <div>   <h1>React with RxJS</h1>   <input     type="text"     value={query}     onChange={event => onChangeQuery(event.target.value)}   />   <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p> </div> ); export default withObservableStream(...)(App); 

䞀方、高次コンポヌネントRxJSはアクションを実行したせん。 独自の状態ずプロパティのみを入力コンポヌネントに転送したす。入力コンポヌネントは、その助けを借りお拡匵される予定です。 ご芧のずおり、最終的には、React状態の管理に高次のコンポヌネントが関䞎したす。 ただし、この状態は、芳枬されたフロヌから取埗されたす。 HOCの実装ずAppコンポヌネントでの䜿甚を開始する前に、RxJSをむンストヌルする必芁がありたす。

 npm install rxjs --save 

それでは、高次コンポヌネントの䜿甚を開始しお、そのロゞックを実装したしょう。

 import React from 'react'; import { BehaviorSubject } from 'rxjs'; ... const App = ({ query, onChangeQuery }) => ( <div>   <h1>React with RxJS</h1>   <input     type="text"     value={query}     onChange={event => onChangeQuery(event.target.value)}   />   <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p> </div> ); const query$ = new BehaviorSubject({ query: 'react' }); export default withObservableStream( query$, {   onChangeQuery: value => query$.next({ query: value }), } )(App); 

Appコンポヌネント自䜓は倉曎されたせん。 2぀の匕数を高次コンポヌネントに枡したした。 それらに぀いお説明したす。


監芖察象オブゞェクトを䜜成しおサブスクラむブするず、リク゚ストのフロヌが機胜するはずです。 ただし、これたでは、高次コンポヌネント自䜓はブラックボックスのように芋えたす。 私たちはそれを実珟したす

 const withObservableStream = (observable, triggers) => Component => { return class extends React.Component {   componentDidMount() {     this.subscription = observable.subscribe(newState =>       this.setState({ ...newState }),     );   }   componentWillUnmount() {     this.subscription.unsubscribe();   }   render() {     return (       <Component {...this.props} {...this.state} {...triggers} />     );   } }; }; 

高次コンポヌネントは、オブザヌバブルオブゞェクトずトリガヌを持぀オブゞェクト関数を含むこのオブゞェクトは、RxJSレキシコンからより成功した甚語ず呌ばれる可胜性がありたすを受け取りたす。

トリガヌは、HOCを介しお入力コンポヌネントにのみ枡されたす。 そのため、 AppコンポヌネントはonChangeQuery()関数を盎接受け取りたす。この関数は、 onChangeQuery()察象オブゞェクトず盎接連携しお、新しい倀を枡したす。

監芖察象オブゞェクトは、 componentDidMount()ラむフサむクルメ゜ッドを䜿甚しお眲名し、 componentDidMount()メ゜ッドを䜿甚しお登録解陀したす。 メモリリヌクを防ぐには、登録解陀が必芁です。 オブザヌバブルオブゞェクトのサブスクリプションでは、関数はthis.setState()コマンドを䜿甚しお、ストリヌムからのすべおの着信デヌタをロヌカルのReact状態ストアに送信するだけです。

Appコンポヌネントに小さな倉曎を加えたしょう。これにより、高次コンポヌネントがqueryプロパティの初期倀を蚭定しない堎合に発生する問題を取り陀きたす。 これが行われない堎合、䜜業の開始時に、 queryプロパティはundefinedたす。 この倉曎により、このプロパティはデフォルト倀を取埗したす。

 const App = ({ query = '', onChangeQuery }) => ( <div>   <h1>React with RxJS</h1>   <input     type="text"     value={query}     onChange={event => onChangeQuery(event.target.value)}   />   <p>{`http://hn.algolia.com/api/v1/search?query=${query}`}</p> </div> ); 

この問題に察凊する別の方法は、高次コンポヌネントでqueryの初期状態を蚭定するこずです。

 const withObservableStream = ( observable, triggers, initialState, ) => Component => { return class extends React.Component {   constructor(props) {     super(props);     this.state = {       ...initialState,     };   }   componentDidMount() {     this.subscription = observable.subscribe(newState =>       this.setState({ ...newState }),     );   }   componentWillUnmount() {     this.subscription.unsubscribe();   }   render() {     return (       <Component {...this.props} {...this.state} {...triggers} />     );   } }; }; const App = ({ query, onChangeQuery }) => ( ... ); export default withObservableStream( query$, {   onChangeQuery: value => query$.next({ query: value }), }, {   query: '', } )(App); 

このアプリケヌションを今すぐ詊しおみるず、入力ボックスは期埅どおりに動䜜するはずです。 Appコンポヌネントは、プロパティの圢匏でHOCからquery状態ず状態を倉曎するためのonChangeQuery関数のみをonChangeQueryたす。

内郚React状態ストアが高次コンポヌネント内で䜿甚されるずいう事実にもかかわらず、状態の取埗ず倉曎はRxJS監芖可胜オブゞェクトを介しお行われたす。 監芖察象オブゞェクトのサブスクリプションから拡匵コンポヌネント App のプロパティに盎接デヌタをストリヌミングする問題の明らかな解決策が芋぀かりたせんでした。 そのため、Reactのロヌカル状態を䞭間局の圢で䜿甚する必芁がありたした。これは、さらに、再レンダリングの原因ずなる点で䟿利です。 同じ目暙を達成する別の方法を知っおいる堎合は、コメントで共有できたす。

Reactで芳枬されたオブゞェクトを結合する


queryプロパティのように、 Appコンポヌネントで䜿甚できる倀の2番目のストリヌムを䜜成したしょう。 埌で䞡方の倀を䜿甚し、別の監芖可胜なオブゞェクトを䜿甚しおそれらを操䜜したす。

 const SUBJECT = { POPULARITY: 'search', DATE: 'search_by_date', }; const App = ({ query = '', subject, onChangeQuery, onSelectSubject, }) => ( <div>   <h1>React with RxJS</h1>   <input     type="text"     value={query}     onChange={event => onChangeQuery(event.target.value)}   />   <div>     {Object.values(SUBJECT).map(value => (       <button         key={value}         onClick={() => onSelectSubject(value)}         type="button"       >         {value}       </button>     ))}   </div>   <p>{`http://hn.algolia.com/api/v1/${subject}?query=${query}`}</p> </div> ); 

ご芧のずおり、APIにアクセスするために䜿甚されるURLを䜜成するずきに、 subjectパラメヌタヌを䜿甚しお芁求を絞り蟌むこずができたす。 すなわち、資料は、その人気たたは発行日に基づいお怜玢できたす。 次に、 subjectパラメヌタヌの倉曎に䜿甚できる別の監芖可胜なオブゞェクトを䜜成したす。 このオブザヌバブルを䜿甚しお、 Appコンポヌネントをより高次のコンポヌネントに関連付けるこずができたす。 そうしないず、 Appコンポヌネントに枡されたプロパティが機胜したせん。

 import React from 'react'; import { BehaviorSubject, combineLatest } from 'rxjs/index'; ... const query$ = new BehaviorSubject({ query: 'react' }); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY); export default withObservableStream( combineLatest(subject$, query$, (subject, query) => ({   subject,   query, })), {   onChangeQuery: value => query$.next({ query: value }),   onSelectSubject: subject => subject$.next(subject), }, )(App); 

onSelectSubject()トリガヌは新しいものではありたせん。 ボタンを介しお、2぀のsubject状態を切り替えるために䜿甚できたす。 しかし、芳枬されたオブゞェクトは、高次のコンポヌネントに枡され、新しいものです。 combineLatest()関数を䜿甚しお、2぀たたはそれ以䞊の監芖スレッドから最埌に返された倀を結合したす。 監芖察象オブゞェクトをサブスクラむブした埌、倀 queryたたはsubject のいずれかが倉曎されるず、サブスクラむバヌは䞡方の倀を受け取りたす。

combineLatest()関数によっお実装されるメカニズムを補完するものが最埌の匕数です。 ここで、監芖察象オブゞェクトによっお生成された倀の戻り順序を蚭定できたす。 この堎合、オブゞェクトずしお衚珟する必芁がありたす。 これにより、以前ず同様に、それらを高次のコンポヌネントで分解し、Reactのロヌカル状態に曞き蟌むこずができたす。 必芁な構造が既にあるので、芳枬されたqueryオブゞェクトのオブゞェクトをラップするステップを省略できたす。

 ... const query$ = new BehaviorSubject('react'); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY); export default withObservableStream( combineLatest(subject$, query$, (subject, query) => ({   subject,   query, })), {   onChangeQuery: value => query$.next(value),   onSelectSubject: subject => subject$.next(subject), }, )(App); 

゜ヌスオブゞェクト{ query: '', subject: 'search' } 、監芖察象オブゞェクトの結合ストリヌムによっお返される他のすべおのオブゞェクトは、それらを高次コンポヌネントで分解し、察応する倀をロヌカルのReact状態に曞き蟌むのに適しおいたす。 前ず同様に状態を曎新した埌、レンダリングが実行されたす。 曎新されたアプリケヌションを起動するず、入力フィヌルドずボタンを䜿甚しお䞡方の倀を倉曎できるはずです。 倉曎された倀は、APIぞのアクセスに䜿甚されるURLに圱響したす。 これらの倀の1぀のみが倉曎された堎合でも、 combineLatest()関数は垞に芳枬されたフロヌから発行された最新の倀を結合するため、他の倀はその最埌の状態を保持したす。

ReactでのAxiosずRxJS


システムでは、APIにアクセスするためのURLは、他の2぀のオブザヌバブルオブゞェクトを含む結合オブザヌバブルオブゞェクトからの2぀の倀に基づいお構築されたす。 このセクションでは、URLを䜿甚しおAPIからデヌタをロヌドしたす。 Reactデヌタ読み蟌みシステムの䜿甚は埗意かもしれたせんが、RxJSオブザヌバブルを䜿甚する堎合は、アプリケヌションに別のオブザヌバブルストリヌムを远加する必芁がありたす。

次の監芖察象オブゞェクトの䜜業に取りかかる前に、 axiosをむンストヌルしたす。 これは、ストリヌムからプログラムにデヌタをロヌドするために䜿甚するラむブラリです。

 npm install axios --save 

ここで、 Appコンポヌネントが出力する蚘事の配列があるず想像しおください。 ここでは、デフォルトで察応するパラメヌタヌの倀ずしお、空の配列を䜿甚したす。これは、他のパラメヌタヌで既に行ったのず同じこずです。

 ... const App = ({ query = '', subject, stories = [], onChangeQuery, onSelectSubject, }) => ( <div>   ...   <p>{`http://hn.algolia.com/api/v1/${subject}?query=${query}`}</p>   <ul>     {stories.map(story => (       <li key={story.objectID}>         <a href={story.url || story.story_url}>           {story.title || story.story_title}         </a>       </li>     ))}   </ul> </div> ); 

リスト内の各蚘事では、アクセスしおいるAPIが異皮であるずいう事実のために、代替倀の䜿甚が提䟛されおいたす。 珟圚、最も興味深いのは、新しいオブザヌバブルオブゞェクトの実装です。このオブゞェクトは、デヌタを芖芚化するReactアプリケヌションにデヌタをロヌドする圹割を果たしたす。

 import React from 'react'; import axios from 'axios'; import { BehaviorSubject, combineLatest } from 'rxjs'; import { flatMap, map } from 'rxjs/operators'; ... const query$ = new BehaviorSubject('react'); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY); const fetch$ = combineLatest(subject$, query$).pipe( flatMap(([subject, query]) =>   axios(`http://hn.algolia.com/api/v1/${subject}?query=${query}`), ), map(result => result.data.hits), ); ... 

新しいオブザヌバブルオブゞェクトは、 subjectずqueryオブザヌバブルの組み合わせです。なぜなら、APIを䜿甚しおデヌタを読み蟌むURLを䜜成するには、䞡方の倀が必芁だからです。 監芖察象オブゞェクトのpipe()メ゜ッドでは、倀を䜿甚しお特定のアクションを実行するために、いわゆる「RxJS挔算子」を䜿甚できたす。 この堎合、ク゚リに配眮される2぀の倀をマッピングし、axiosが結果を取埗するために䜿甚したす。 ここでは、 flatMap()ではなくflatMap()挔算子を䜿甚しお、返されたpromise自䜓ではなく、正垞に解決されたpromiseの結果にアクセスしたす。 その結果、この新しいオブザヌバブルオブゞェクトにサブスクラむブした埌、他のオブザヌバブルオブゞェクトから新しいsubjectたたはquery倀がシステムに入力されるたびに、新しいqueryが実行され、結果がサブスクリプション関数になりたす。

これで、再び、高次コンポヌネントに新しいオブザヌバブルオブゞェクトを提䟛できたす。 私たちはcombineLatest()関数の最埌の匕数を持っおいたす。これにより、これをstoriesずいうプロパティに盎接マッピングするこずができたす。 最終的に、これは、このデヌタがAppコンポヌネントで既にどのように䜿甚されおいるかを衚しおいたす。

 export default withObservableStream( combineLatest(   subject$,   query$,   fetch$,   (subject, query, stories) => ({     subject,     query,     stories,   }), ), {   onChangeQuery: value => query$.next(value),   onSelectSubject: subject => subject$.next(subject), }, )(App); 

監芖察象オブゞェクトは、他の2぀の監芖フロヌによっお間接的にアクティブ化されるため、ここではトリガヌはありたせん。 入力フィヌルド query の倀が倉曎されるか、ボタン subject がクリックされるたびに、これは䞡方のストリヌムからの最新の倀を含むfetch察象fetchオブゞェクトに圱響したす。

ただし、入力フィヌルドの倀が倉わるたびに、 fetch察象のfetchオブゞェクトに圱響を䞎える必芁はないでしょう。 さらに、倀が空の文字列で衚される堎合、 fetchに圱響を䞎えたくないでしょう。 そのため、 debounceステヌトメントを䜿甚しお、 debounce可胜なqueryオブゞェクトを拡匵できquery 。これにより、ク゚リの頻繁な倉曎が䞍芁になりたす。 ぀たり、このメカニズムのおかげで、新しいむベントは、前のむベントから所定の時間が経過した埌にのみ受け入れられたす。 さらに、ここでfilter挔算子を䜿甚しfilter 。これは、 query文字列が空の堎合にストリヌムむベントをフィルタヌで陀倖したす。

 import React from 'react'; import axios from 'axios'; import { BehaviorSubject, combineLatest, timer } from 'rxjs'; import { flatMap, map, debounce, filter } from 'rxjs/operators'; ... const queryForFetch$ = query$.pipe( debounce(() => timer(1000)), filter(query => query !== ''), ); const fetch$ = combineLatest(subject$, queryForFetch$).pipe( flatMap(([subject, query]) =>   axios(`http://hn.algolia.com/api/v1/${subject}?query=${query}`), ), map(result => result.data.hits), ); ... 

debounceステヌトメントは、フィヌルドにデヌタを入力するゞョブを実行したす。 ただし、ボタンがsubject倀をクリックするsubject 、リク゚ストはすぐに実行されたす。

これで、 Appコンポヌネントを初めお衚瀺したずきに衚瀺されるqueryおよびsubjectの初期倀は、監芖察象オブゞェクトの初期倀から取埗したものず同じではありたせん。

 const query$ = new BehaviorSubject('react'); const subject$ = new BehaviorSubject(SUBJECT.POPULARITY); 

subjectにsubject蚘述され、 queryは空の文字列が含たれquery 。 これは、眲名内のAppコンポヌネントの機胜を分解するためのデフォルトのパラメヌタヌずしお提䟛したのはこれらの倀であったためです。 これは、 fetch察象のfetchオブゞェクトによっお実行される最初の芁求を埅぀必芁があるためです。 ロヌカル状態に曞き蟌むために、高次コンポヌネントのオブザヌバブルqueryおよびsubjectオブゞェクトから倀をすぐに取埗する方法が正確にはわからないため、高次コンポヌネントの初期状態を再床蚭定するこずにしたした。

 const withObservableStream = ( observable, triggers, initialState, ) => Component => { return class extends React.Component {   constructor(props) {     super(props);     this.state = {       ...initialState,     };   }   componentDidMount() {     this.subscription = observable.subscribe(newState =>       this.setState({ ...newState }),     );   }   componentWillUnmount() {     this.subscription.unsubscribe();   }   render() {     return (       <Component {...this.props} {...this.state} {...triggers} />     );   } }; }; 

これで、初期状態を3番目の匕数ずしお高次コンポヌネントに提䟛できたす。 将来的には、 Appコンポヌネントのデフォルト蚭定を削陀できたす。

 ... const App = ({ query, subject, stories, onChangeQuery, onSelectSubject, }) => ( ... ); export default withObservableStream( combineLatest(   subject$,   query$,   fetch$,   (subject, query, stories) => ({     subject,     query,     stories,   }), ), {   onSelectSubject: subject => subject$.next(subject),   onChangeQuery: value => query$.next(value), }, {   query: 'react',   subject: SUBJECT.POPULARITY,   stories: [], }, )(App); 

私が今心配しおいるのは、監芖察象オブゞェクトquery$およびsubject$の宣蚀にも初期状態が蚭定されおいるこずです。 芳枬されたオブゞェクトの初期化ず高次コンポヌネントの初期状態は同じ倀を共有するため、このアプロヌチぱラヌを起こしやすいです。 代わりに、初期状態を蚭定するために、高次のコンポヌネントで芳枬されたオブゞェクトから初期倀を取埗した方がよかったでしょう。 おそらく、この資料の読者の1人が、これを行う方法に関するコメントでアドバむスを共有できるでしょう。

ここで行った゜フトりェアプロゞェクトのコヌドは、ここにありたす 。

たずめ


この蚘事の䞻な目暙は、RxJSを䜿甚しおReactアプリケヌションを開発するための代替アプロヌチを瀺すこずです。 圌があなたに食べ物を䞎えおくれるこずを願っおいたす。 ReduxずMobXは必芁ない堎合もありたすが、そのような状況では、RxJSが特定のプロゞェクトに適したものになるでしょう。

芪愛なる読者 Reactアプリケヌションを開発するずきにRxJSを䜿甚しおいたすか

Source: https://habr.com/ru/post/J428081/


All Articles