2016-03-07 3 views
17

Prendiamo una classe come questa in un'app con React e React Router.Come collegare lo stato agli oggetti di scena con mobx.js @observer quando si usa la classe ES6?

@observer class Module1 extends React.Component { 

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

    componentWillMount(){ 
    //... 
    } 

    method(){ 
    //... 
    } 

    otherMethod(){ 
    //... 
    } 

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

E diamo uno stato come questo

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

Il componente Module1 è caricato in questo modo:

//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') 
    ); 

Come potrei passare i puntelli module1.bars alla componente Module1? In redux userei <provider> e redux-connect ma sono un po 'perso con questo in Mobx.js.

risposta

8

In primo luogo, ecco un semplice esempio app che fa il routing utilizzando MOBX, reagire e reagire-router: https://github.com/contacts-mvc/mobx-react-typescript

In generale, personalmente mi piace passare esplicitamente tutti i negozi importanti come oggetti di scena espliciti ai miei componenti. Ma puoi anche usare un pacchetto come Ryan per far passare i tuoi negozi ai tuoi componenti usando il meccanismo di contesto React, simile a Redux connect (vedi questo app per un esempio).

Una volta che hai il tuo negozio nel componente, analizza i parametri di instradamento in ComponentWillMount e aggiorna i tuoi negozi di conseguenza.

Questo dovrebbe essere praticamente tutto :) Ma fammi sapere se lascio che qualcosa non abbia risposta.

+0

Il mobx reagisce con un esempio di dattiloscritto illustra come passarli esplicitamente? – dagatsoin

+0

Forse ho un'idea sbagliata sull'impatto sulle prestazioni per passare un intero negozio in un componente superiore. Forse la risposta alla mia domanda è un'altra domanda: è buona pratica passare un intero negozio a un componente. E la risposta ovvia è sì. Lo faccio già con il provider di redux ... Confermate? – dagatsoin

+2

Sì, confermo. Un negozio è solo un riferimento. Dovrebbe essere molto efficiente per passare in giro :) Garantisce inoltre che tutte le API del modello siano disponibili nel componente. – mweststrate

40

Una settimana fa abbiamo iniziato un nuovo progetto con con reagiscono e MOBX, e ho affrontato lo stesso problema ai suoi. Dopo aver guardato in giro ho scoperto che il modo migliore è usare il contesto di react. Ecco come:

Il negozio: 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(); 

Nota: stiamo usando Singleton per assicurarsi che è solo un caso, perché il negozio può essere utilizzato al di fuori reagire componenti, ad es. routes.js

I percorsi: 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> 
); 

Il componente principale: 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; 
    } 
} 

E, infine, un componente usando il negozio: 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> 
    ); 
    } 
} 

è possibile dichiarare nuovi negozi e aggiungili in getChildContext di App.js e ogni volta che è necessario un determinato negozio dichiarare la dipendenza store nel componente contextTypes e ottenerlo da this.context.

ho notato che non è un requisito per passare un osservabile come puntello, soltanto con il @observer decoratore e utilizzando qualsiasi osservabile valore nel componente, mobx e mobx-react fare la loro magia.

A proposito, lo <Provider store={myStore}><App /></Provider> di redux fa la stessa cosa spiegata in App.js. https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context

Riferimento:

+0

Grazie per aver condiviso! – mweststrate

+0

davvero un'ottima soluzione, grazie mille! – Alex

+0

Ottima soluzione grazie! Domanda: perché ritorni !! this.user'? È diverso da fare semplicemente "return this.user"? –

4

Date un'occhiata a react-tunnel. Ti dà un componente Provider e il decoratore inject (funziona come connect in redux).

15

mobx-react offre un (sperimentale - al momento della stesura di questo) Provider (componente) e inject (componente di ordine superiore) per passare le proprietà alla gerarchia dei componenti di seguito.

Da sopra è possibile utilizzare il componente Provider per passare tutte le informazioni pertinenti. Sotto il cappuccio viene usato il contesto React.

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') 
); 

In SomeComponent è possibile recuperare le proprietà passarono utilizzando il 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; 

[Disclaimer: ho scritto su di esso in MobX React: Simplified State Management in React e si può vedere un minimal boilerplate application che consuma l'API SoundCloud.]

Problemi correlati