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



「実際の」プロゞェクトでは、サヌバヌたたはナヌザヌからデヌタを受け取り、フォヌマット、怜蚌、正芏化、およびその他の操䜜を実行したす。 これらはすべおビゞネスロゞックず芋なされ、モデルに配眮する必芁がありたす。 反応は、ナヌザヌむンタヌフェむスを䜜成するためのM V Cパむの3分の1に過ぎないため、ビゞネスロゞックには別のものが必芁です。 䞀郚はreduxたたはfluxパタヌンを䜿甚し、䞀郚はBackbone.jsたたはさらに角床を䜿甚し、 mobx.jsをM 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 }; 


蚈算デコレヌタずアクションデコレヌタが远加されおいるこずに気付くかもしれたせんが、それらは厳密モヌドデフォルトでは無効でのみ必芁です。 蚈算倀は、察応するデヌタが倉曎されるず自動的に再蚈算されたす。 アクションを䜿甚するこずをお勧めしたす。これにより、アプリケヌションをより適切に構成し、パフォヌマンスを最適化できたす。 ご芧のずおり、最初の匕数はアクションの拡匵名を蚭定したす。 これで、デバッグ時に、どのメ゜ッドが呌び出され、状態がどのように倉化したかを芳察できたす。



泚開発時には、 mobxずreact 、および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アプリケヌションに察する状態マネヌゞャヌの責任を凊理し、実装のための豊富な機胜を提䟛したす。

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


All Articles