web-dev-qa-db-fra.com

Comment connecter l'état aux accessoires avec mobx.js @observer lors de l'utilisation de la classe ES6?

Prenons une classe comme celle-ci dans une application avec React et React Router.

@observer class Module1 extends React.Component {

  constructor (props) {
    super(props);
    //...
  }

  componentWillMount(){
    //...
  }

  method(){
    //...
  }

  otherMethod(){
    //...
  }

  render() {
    return (
       <ChildComp bars={this.props.bars}/>}
    );
  }
}

Et prenons un état comme celui-ci

state = observable({
  module1:{
    bars:{
      //...
    }
  },
  module2:{
    foos:{
      //...
    }
  }
})

Le composant Module1 est chargé comme ceci:

//index.js
render(
      <Router history={browserHistory}>
        <Route path="/" component={App}>
          <Route path='/map' component={Module1} >
            <Route path="/entity/:id" component={SubModule}/>
          </Route>
          <Route path='/map' component={Module2} >
        </Route>
      </Router>,
      document.getElementById('render-target')
    );

Comment pourrais-je passer les accessoires module1.bars au composant Module1? Dans redux, j'utiliserais <provider>et redux-connect mais je suis un peu perdu avec ça dans Mobx.js.

20
dagatsoin

Tout d'abord, voici un exemple d'application simple qui fait du routage à l'aide de MobX, React et react-router: https://github.com/contacts-mvc/mobx-react-TypeScript

En général, personnellement, j'aime transmettre explicitement tous les magasins concernés en tant qu'accessoires explicites à mes composants. Mais vous pouvez également utiliser un package comme Ryan pour que vos magasins soient transmis à vos composants en utilisant le mécanisme de contexte React, similaire à Redux connect (voir ceci app pour un exemple).

Une fois que vous avez votre magasin dans votre composant, analysez les paramètres de routage dans ComponentWillMount et mettez à jour vos magasins en conséquence.

Cela devrait être tout simplement :) Mais faites-moi savoir si je laisse quelque chose sans réponse.

10
mweststrate

Il y a une semaine, nous avons commencé un nouveau projet avec avec react et mobx , et j'ai fait face au même problème que le vôtre. Après avoir regardé autour de moi, j'ai trouvé que le meilleur moyen était d'utiliser le contexte de react . Voici comment:

Le magasin: stores/Auth.js

import { get, post } from 'axios';
import { observable, computed } from 'mobx';
import jwt from 'jsonwebtoken';
import singleton from 'singleton';

import Storage from '../services/Storage';

class Auth extends singleton {
  @observable user = null;
  @computed get isLoggedIn() {
    return !!this.user;
  }

  constructor() {
    super();

    const token = Storage.get('token');

    if (token) {
      this.user = jwt.verify(token, JWT_SECRET);
    }
  }

  login(username, password) {
    return post('/api/auth/login', {
      username, password
    })
    .then((res) => {
      this.user = res.data.user;
      Storage.set('token', res.data.token);
      return res;
    });
  }

  logout() {
    Storage.remove('token');
    return get('/api/auth/logout');
  }
}

export default Auth.get();

Remarque: nous utilisons singleton pour nous assurer qu'il ne s'agit que d'une seule instance, car le magasin peut être utilisé en dehors des composants React, par exemple. routes.js

Les itinéraires: routes.js

import React from 'react';
import { Route, IndexRoute } from 'react-router';

import App from './App';
import Login from './Login/Login';
import Admin from './Admin/Admin';
import Dashboard from './Admin/views/Dashboard';
import Auth from './stores/Auth'; // note: we can use the same store here..

function authRequired(nextState, replace) {
  if (!Auth.isLoggedIn) {
    replace('/login');
  }
}

export default (
  <Route name="root" path="/" component={App}>
    <Route name="login" path="login" component={Login} />
    <Route name="admin" path="admin" onEnter={authRequired} component={Admin}>
      <IndexRoute name="dashboard" component={Dashboard} />
    </Route>
  </Route>
);

Le composant principal: App.js

// App.js
import React, { Component } from 'react';
import Auth from './stores/Auth';

export default class App extends Component {

  static contextTypes = {
    router: React.PropTypes.object.isRequired
  };

  static childContextTypes = {
    store: React.PropTypes.object
  };

  getChildContext() {
    /**
     * Register stores to be passed down to components
     */
    return {
      store: {
        auth: Auth
      }
    };
  }

  componentWillMount() {
    if (!Auth.isLoggedIn) {
      this.context.router.Push('/login');
    }
  }

  render() {
    return this.props.children;
  }
}

Et enfin, un composant utilisant le magasin: Login.js

import React, { Component } from 'react';
import { observer } from 'mobx-react';

@observer
export default class Login extends Component {

  static contextTypes = {
    router: React.PropTypes.object.isRequired,
    store: React.PropTypes.object.isRequired
  };

  onSubmit(e) {
    const { auth } = this.context.store; // this is our 'Auth' store, same observable instance used by the `routes.js`

    auth.login(this.refs.username.value, this.refs.password.value)
      .then(() => {
        if (auth.isLoggedIn) this.context.router.Push('/admin');
      })
      .catch((err) => {
        console.log(err);
      });

    e.preventDefault();
  }

  render() {
    return (
      <div className="login__form">
        <h2>Login</h2>
        <form onSubmit={this.onSubmit.bind(this)}>
          <input type="text" ref="username" name="username" placeholder="Username" />
          <input type="password" ref="password" name="password" placeholder="Password" />
          <button type="submit">Login</button>
        </form>
      </div>
    );
  }
}

Vous pouvez déclarer de nouveaux magasins et les ajouter dans getChildContext de App.js, et chaque fois que vous avez besoin d'un certain magasin, déclarez simplement la dépendance store dans le contextTypes du composant, et récupérez-la à partir de this.context.

J'ai remarqué qu'il n'est pas obligatoire de passer un observable comme accessoire, juste en ayant le @observer décorateur et en utilisant n'importe quelle valeur observable dans votre composant, mobx et mobx-react faire leur magie.

Soit dit en passant, <Provider store={myStore}><App /></Provider> fait la même chose que celle expliquée dans App.js. https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context

Référence:

42
thaerlabs

mobx-react offre un (expérimental - au moment d'écrire ceci) Provider (composant) et inject (composant d'ordre supérieur) pour passer des propriétés à la hiérarchie des composants au dessous de.

Par le haut, vous pouvez utiliser le composant Provider pour transmettre toutes les informations pertinentes. Sous le capot React est utilisé.

import { Provider } from 'mobx-react';
...
import oneStore from './stores/oneStore';
import anotherStore from './stores/anotherStore';

const stores = { oneStore, anotherStore };

ReactDOM.render(
  <Provider { ...stores }>
    <Router history={browserHistory}>
      <Route path="/" component={App}>
        <Route path="/" component={SomeComponent} />
      </Route>
    </Router>
  </Provider>,
  document.getElementById('app')
);

Dans SomeComponent, vous pouvez récupérer les propriétés transmises en utilisant le inject HOC:

import { observer, inject } from 'mobx-react';
...

const SomeComponent = inject('oneStore', 'anotherStore')(observer(({ oneStore, anotherStore }) => {
  return <div>{oneStore.someProp}{anotherStore.someOtherProp}</div>;
}))

export default SomeComponent;

[Avertissement: J'ai écrit à ce sujet dans MobX React: Simplified State Management dans React et vous pouvez voir un application minimale standard qui consomme l'API SoundCloud.]

15
Robin Wieruch

Jetez un œil à react-tunnel . Il vous donne un composant Provider et le décorateur inject (fonctionne comme connect dans redux).

4
kuuup