最初からReact + mobxパス。 Mobx +反応、側面図





「実際の」プロジェクトでは、サーバーまたはユーザーからデータを受け取り、フォーマット、検証、正規化、およびその他の操作を実行します。 これらはすべてビジネスロジックと見なされ、モデルに配置する必要があります。 反応は、ユーザーインターフェイスを作成するためのM V Cパイの3分の1に過ぎないため、ビジネスロジックには別のものが必要です。 一部はreduxまたはfluxパターンを使用し、一部はBackbone.jsまたはさらに角度を使用し、 mobx.jsM odelとして使用します。



前の記事で、私たちはすでに基盤を準備しました。それを基に構築します。 mobxはスタンドアロンライブラリであるため、reactとリンクするにはmobx-reactが必要です



npm i --save mobx mobx-react
      
      





さらに、デコレータを操作してクラスプロパティを変換するには、babelプラグインbabel-plugin-transform-class-propertiesおよびbabel-plugin-transform-decorators-legacyが必要です。



 npm i --save-dev babel-plugin-transform-decorators-legacy babel-plugin-transform-class-properties
      
      





それらを.babelrcに追加することを忘れないでください



  "plugins": [ "react-hot-loader/babel", "transform-decorators-legacy", "transform-class-properties" ]
      
      





Menuコンポーネントがあります。引き続き作業を続けましょう。 パネルには2つの「オープン/クローズ」状態があり、mobxを使用して状態を管理します。



1.最初に、@ observableデコレーターを追加して、状態判別し、観測可能にする必要があります。 状態は、オブジェクト、配列、クラスなどの任意のデータ構造で表すことができます。 storesディレクトリにメニューのストア(menu-store.js)を作成します。



 import { observable} from 'mobx'; class MenuStore { @observable show; constructor() { this.show = false; } } export default new MenuStore();
      
      





Storeは、単一のショープロパティを持つES6クラスです。 @observableデコレータをそれに掛けて、mobxにそれを見るように伝えました。 Showは、変更するパネルの状態です。



2. 状態の変化に対応するビューを作成します 。 既に持っているのは良いことです。コンポーネント/メニュー/index.jsです。 これで、状態が変化すると、メニューが自動的にリダイレクトされますが、mobxはビューを更新する最短の方法を見つけます。 このためには、オブザーバーで反応コンポーネントを記述する関数をラップする必要があります。



コンポーネント/メニュー/index.js
 import React from 'react'; import cn from 'classnames'; import { observer } from 'mobx-react'; /* stores */ import menuStore from '../../stores/menu-store'; /* styles */ import styles from './style.css'; const Menu = observer(() => ( <nav className={cn(styles.menu, { [styles.active]: menuStore.show })}> <div className={styles['toggle-btn']}></div> </nav> )); export default Menu;
      
      







反応アプリケーションでは、classNameを操作するためにclassnamesユーティリティが必要です。 以前は、reactパッケージの一部でしたが、現在は個別にインストールされます。



 npm i --save classnames
      
      





その助けを借りて、さまざまな条件を使用してクラス名を接着することができます。これはかけがえのないことです。

メニュー状態の値が=== trueを示す場合、クラス「アクティブ」を追加することがわかります。 このコンストラクターの状態をthis.show = trueに変更すると、パネルには「アクティブな」クラスが含まれます。



3. 状態を変更する必要があります 。 でハンバーガーのクリックイベントを追加します

メニュー/index.js
 <div onClick={() => { menuStore.toggleLeftPanel() }} className={styles['toggle-btn']}>☰</div>
      
      





およびtoggleLeftPanel()メソッド

ストア/ menu-store.js
 import { observable } from 'mobx'; class MenuStore { @observable show; constructor() { this.show = false; } toggleLeftPanel() { this.show = !this.show; } } const menuStore = new MenuStore(); export default menuStore; export { MenuStore };
      
      







注:デフォルトでは、ストレージをシングルトンインスタンスとしてエクスポートします。クラスは、テストなどにも必要になる場合があるため、直接エクスポートされます。



明確にするために、スタイルを追加します。



コンポーネント/メニュー/styles.css
 .menu { position: fixed; top: 0; left: -180px; bottom: 0; width: 220px; background-color: tomato; &.active { left: 0; } & .toggle-btn { position: absolute; top: 5px; right: 10px; font-size: 26px; font-weight: 500; color: white; cursor: pointer; } }
      
      







そして、アイコンをクリックして、パネルが開閉することを確認してください。 パネルの状態を制御するために、最小限のmobxストアを作成しました。 肉を作り、別のコンポーネントからパネルを制御してみましょう。 パネルを開いたり閉じたりするには、追加のメソッドが必要です。



ストア/ menu-store.js
 import { observable, computed, action } from 'mobx'; class MenuStore { @observable show; constructor() { this.show = false; } @computed get isOpenLeftPanel() { return this.show; } @action('toggle left panel') toggleLeftPanel() { this.show = !this.show; } @action('show left panel') openLeftPanel() { this.show = true; } @action('hide left panel') closeLeftPanel() { this.show = false; } } const menuStore = new MenuStore(); export default menuStore; export { MenuStore };
      
      







計算デコレータとアクションデコレータが追加されていることに気付くかもしれませんが、それらは厳密モード(デフォルトでは無効)でのみ必要です。 計算値は、対応するデータが変更されると自動的に再計算されます。 アクションを使用することをお勧めします。これにより、アプリケーションをより適切に構成し、パフォーマンスを最適化できます。 ご覧のとおり、最初の引数はアクションの拡張名を設定します。 これで、デバッグ時に、どのメソッドが呼び出され、状態がどのように変化したかを観察できます。







注:開発時には、 mobxreact 、およびreact-mobx devtoolsにChrome拡張機能を使用すると便利です



別のコンポーネントを作成する

コンポーネント/左パネルコントローラー/ index.js
 import React from 'react'; /* stores */ import menuStore from '../../stores/menu-store'; /* styles */ import styles from './styles.css'; const Component = () => ( <div className={styles.container}> <button onClick={()=>{ menuStore.openLeftPanel(); }}>Open left panel</button> <button onClick={()=>{ menuStore.closeLeftPanel(); }}>Close left panel</button> </div> ); export default Component;
      
      







パネルを開閉するボタンのペア内。 このコンポーネントをホームページに追加します。 以下を取得する必要があります。



構造




ブラウザでは、次のようになります。



職場でのmobx




これで、パネル自体からだけでなく、別のコンポーネントからもパネルの状態を制御できます。

注:たとえば、「左パネルを閉じる」ボタンをクリックして同じアクションを複数回実行した場合、デバッガーでアクションが機能することを確認できますが、反応は発生しません。 これは、状態が変更されておらず、純粋な反応コンポーネントの場合のように「余分な」コードを記述する必要がないため、mobxはコンポーネントをリダイレクトしないことを意味します。



私たちのアプローチを少しばかり検討することは残っています。店舗と協力するのは良いことですが、プロジェクト全体で倉庫の輸入を分散させるのはいです。 mobx-reactでは、 Providerはそのような目的のために登場しました(Providerおよびinjectを参照) 。reactcontextを使用してストアを(だけでなく)子孫に渡すことができるコンポーネントです。 これを行うには、app.jsのルートコンポーネントをプロバイダーでラップします。



app.js
 import React from 'react'; import { Provider } from 'mobx-react'; import { useStrict } from 'mobx'; /* components */ import Menu from '../components/menu'; /* stores */ import leftMenuStore from '../stores/menu-store'; /* styles */ import './global.css'; import style from './app.css'; useStrict(true); const stores = { leftMenuStore }; const App = props => ( <Provider { ...stores }> <div className={style['app-container']}> <Menu /> <div className={style['page-container']}> {props.children} </div> </div> </Provider> ); export default App;
      
      







すぐにすべての関係者(1つがあります)をインポートし、小道具を介してプロバイダーに転送します。 プロバイダーはコンテキストを操作するため、関係者はすべての子コンポーネントで利用できます。 また、menu.jsコンポーネントを2つに分割して、 「愚かな」コンポーネントと「スマートな」コンポーネントを取得します



コンポーネント/メニュー/menu.js
 import React from 'react'; import cn from 'classnames'; import styles from './style.css'; const Menu = props => ( <nav className={cn(styles.menu, { [styles.active]: props.isOpenLeftPanel })}> <div onClick={props.toggleMenu} className={styles['toggle-btn']}></div> </nav> ); export default Menu;
      
      







コンポーネント/メニュー/index.js
 import React from 'react'; import { observer, inject } from 'mobx-react'; import Menu from './menu' const Component = inject('leftMenuStore')(observer(({ leftMenuStore }) => ( <Menu toggleMenu={() => leftMenuStore.toggleLeftPanel()} isOpenLeftPanel={leftMenuStore.isOpenLeftPanel} /> ))); Component.displayName = "MenuContainer"; export default Component;
      
      







「Silly」は、パネルと切り替え用のコールバックが開いているか閉じているかに関するデータを小道具を通じて受け取る通常のステートレスコンポーネントであるため、私たちにとって興味深いものではありません。



彼のラッパーを見るともっと面白いです。ここではHOCを見て、必要なストア、この場合は「leftMenuStore」をオブザーバーにラップされた「愚かなコンポーネント」に渡すコンポーネントとして注入します。 leftMenuStoreを注入したので、リポジトリは小道具を介して利用可能になりました。



左パネルコントローラーで行うこととほぼ同じこと:



コンポーネント/ left-menu-controller / left-menu-controller.js
 import React from 'react'; /* styles */ import style from './styles.css'; const LeftPanelController = props => ( <div className={style.container}> <button onClick={() => props.openPanel()}>Open left panel</button> <button onClick={() => props.closePanel()}>Close left panel</button> </div> ); export default LeftPanelController;
      
      







コンポーネント/左メニューコントローラー/ index.js
 import React from 'react'; import { inject } from 'mobx-react'; import LeftPanelController from './left-panel-controller'; const Component = inject('leftMenuStore')(({ leftMenuStore }) => { return ( <LeftPanelController openPanel={() => leftMenuStore.openLeftPanel()} closePanel={() => leftMenuStore.closeLeftPanel()} /> ); }); LeftPanelController.displayName = 'LeftPanelControllerContainer'; export default Component;
      
      







唯一の違いは、このコンポーネントに対して何も再描画する必要がないため、オブザーバーを使用しないことです。リポジトリのopenLeftPanel()およびcloseLeftPanel()メソッドのみが必要です。



注:displayNameを使用してコンポーネントに名前を付けます。これはデバッグに便利です。



たとえば、検索を通じてコン​​ポーネントを見つけることができるようになりました




すべて簡単です。サーバーからデータを取得し、チェックボックス付きのユーザーのリストにしましょう。



サーバーにアクセスし、「/ users」ルートを追加してユーザーを取得します。



server.js
 const USERS = [ { id: 1, name: "Alexey", age: 30 }, { id: 2, name: "Ignat", age: 15 }, { id: 3, name: "Sergey", age: 26 }, ]; ... app.get("/users", function(req, res) { setTimeout(() => { res.send(USERS); }, 1000); });
      
      







意図的に遅延を追加して、サーバー応答の間隔が長くてもアプリケーションが正常に動作していることを確認します。



次に必要



ユーザーストア:
 import { observable, computed, action, asMap, autorun } from 'mobx'; class User { @observable user = observable.map(); constructor(userData = {}, checked = false) { this.user.merge(userData); this.user.set("checked", checked); } @computed get userInfo() { return `${this.user.get("name")} - ${this.user.get("age")}`; } @action toggle() { this.user.set("checked", !this.user.get("checked")); } } class UserStore { @observable users; constructor() { this.users = []; this.fetch(); } @computed get selectedCount() { return this.users.filter(userStore => { return userStore.user.get("checked"); }).length; } getUsers() { return this.users; } @action fetch() { fetch('/users', { method: 'GET' }) .then(res => res.json()) .then(json => this.putUsers(json)); } @action putUsers(users) { let userArray = []; users.forEach(user => { userArray.push(new User(user)); }); this.users = userArray; } } const userStore = new UserStore(); autorun(() => { console.log(userStore.getUsers().toJS()); }); export default userStore; export { UserStore };
      
      







ここでは、ユーザープロパティを持つUserクラスについて説明します。 Mobxにはobservable.mapデータ型があり、ユーザーを説明するのにちょうどいいです。 大まかに言うと、観測可能なオブジェクトを取得します。さらに、特定のフィールドの変化を観測できます。 ゲッター、セッター、およびその他のヘルパーメソッドも使用できます。 たとえば、「merge」を使用するコンストラクターでは、userDataからユーザーにフィールドを簡単にコピーできます。 オブジェクトに多くのフィールドが含まれる場合、これは非常に便利です。 また、ユーザーに関する情報を取得するために、ユーザーの状態と計算値を切り替えるアクションを1つ作成します。



以下は、監視対象がユーザーの配列である側自体について説明しています。 コンストラクターで、メソッドをプルしてサーバーからユーザーを取得し、アクションputUsersを介して空の配列にユーザーを入力します。 最後に、チェックされたユーザーの計算された数を返すメソッドを追加します。



注:観測値が変更された場合、 autorunは関数を自動的に実行します。 たとえば、ここではすべてのユーザーがコンソールに表示されます。 getUsers()メソッドを使用してユーザーを取得しようとすると、戻り値の型は配列ではなく、ObservableArrayであることがわかります。 オブザーバブルオブジェクトをjavascript構造に変換するには、 toJS()を使用します。



app.jsでは、子孫が使用できるように新しいユーザーストアを追加することを忘れないでください。



Reactコンポーネントをコンポーネントディレクトリに追加します。



ユーザーリスト/ index.js
 import React from 'react'; import { observer, inject } from 'mobx-react'; import UserList from './user-list'; const Component = inject('userStore')(observer(({ userStore }) => { return ( <UserList users={userStore.getUsers()} selectedUsersCount={userStore.selectedCount} /> ); })); Component.displayName = 'UserList'; export default Component;
      
      







ここではすでにラッパーに精通しており、ユーザーの配列とチェックされたユーザーの数を小道具を介して転送します。



user-list / user-list.js
 import React from 'react'; /* components */ import UserListItem from './user-list-item'; /* styles */ import style from './styles.css'; const UserList = props => { return ( <div className={style.container}> <ul> {props.users.map(userStore => { return ( <UserListItem key={userStore.user.get('id')} isChecked={userStore.user.get('checked')} text={userStore.userInfo} onToggle={() => userStore.toggle()} />); })} </ul> <span>{`Users:${props.users.length}`}</span> <span>{`Selected users: ${props.selectedUsersCount}`}</span> </div> ); }; export default UserList;
      
      







ユーザーのリストとその番号に関する情報を表示します。 ストアのtoggle()メソッドをprops経由で渡します。



user-list / user-list-item.js
 import React from 'react'; const UserListItem = props => ( <li><input type="checkbox" checked={props.isChecked} onClick={() => props.onToggle()} />{props.text} </li> ); export default UserListItem;
      
      







1人のユーザーをレンダリングします。



スタイルを追加し、完成したコンポーネントをホームページにフックします。 すべての準備ができました( github )。チェックボックスを操作して、すべてのメソッドが機能することを確認できます。



その結果、mobxのすべての可能性を考慮して、mobxがリアクションと連携して動作する方法を見ました。そのようなソリューションには生命権があると仮定できます。 Mobxは、Reactアプリケーションに対する状態マネージャーの責任を処理し、実装のための豊富な機能を提供します。



All Articles