2016-03-07 1 views
17

Давайте рассмотрим такой класс в приложении с React and React Router.Как подключить состояние к реквизитам с помощью mobx.js @observer при использовании класса ES6?

@observer class Module1 extends React.Component { 

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

    componentWillMount(){ 
    //... 
    } 

    method(){ 
    //... 
    } 

    otherMethod(){ 
    //... 
    } 

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

И давайте состояние, как этому

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

Компонент Module1 загружаются следующим образом:

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

Как я мог пройти реквизит module1.bars к компоненту Module1? В redux я бы использовал <provider> и redux-connect, но я немного потерялся с этим в Mobx.js.

ответ

8

Во-первых, вот простой пример приложения, которое делает маршрутизацию с использованием MobX, React и реагировать-маршрутизатор: https://github.com/contacts-mvc/mobx-react-typescript

В общем, лично я хотел бы явно передать все соответствующие магазины, как явных реквизита моих компонентов. Но вы также можете использовать пакет, такой как Ryan, чтобы ваши магазины передавались вашим компонентам с использованием механизма контекста React, аналогичного Redux-соединению (см. Пример app).

Как только у вас есть свой магазин в вашем компоненте, проанализируйте параметры маршрутизации в ComponentWillMount и соответствующим образом обновите свои магазины.

Это должно быть в основном все :) Но дайте мне знать, если я позволю чему-нибудь без ответа.

+0

Является ли пример ввода символов mobx примером того, как передать их явно? – dagatsoin

+0

Возможно, у меня неправильная идея о влиянии производительности на передачу всего магазина в верхнем компоненте. Возможно, ответ на мой вопрос - это еще один вопрос: хорошая практика - передать весь магазин компоненту. И очевидный ответ - да. Я уже делаю это с поставщиком redux ... Вы подтверждаете? – dagatsoin

+2

Да, я подтверждаю. Магазин - это просто ссылка. Должно быть очень эффективным, чтобы обойти :) Это также гарантирует, что у вас есть все доступные «api» модели, которые вам могут понадобиться в вашем компоненте. – mweststrate

40

Неделю назад мы начали новый проект с с реагируют и mobx, и я столкнулся с такой же вопрос, как ваша. Оглянувшись, я нашел, что лучший способ - использовать контекст для реагирования. Вот как:

Магазин: 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(); 

Примечание: мы используем синглтон, чтобы убедиться, что это только один пример, потому что магазин может быть использован вне реагируют компоненты, например. routes.js

Маршруты: 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> 
); 

Основной компонент: 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; 
    } 
} 

И, наконец, компонент с помощью магазина: 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> 
    ); 
    } 
} 

Вы можете объявить новые магазины и добавить их в getChildContext от App.js, и всякий раз, когда вам нужен определенный магазин, просто объявите зависимость store в компоненте contextTypes и получите его от this.context.

я заметил, что это не является обязательным требование, чтобы передать наблюдаемую как опора, только при наличии @observer декоратора и с использованием любого наблюдаемой значения в компоненте, mobx и mobx-react делают свою магию.

Кстати, <Provider store={myStore}><App /></Provider>, приведенный в App.js, делает то же самое. https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context

Ссылка:

+0

Спасибо за обмен! – mweststrate

+0

действительно отличное решение, спасибо большое! – Alex

+0

Отличное решение спасибо! Вопрос: почему вы «возвращаетесь !! this.user»? Разве это не так, как просто делать 'return this.user'? –

4

Посмотрите на react-tunnel. Он дает вам компонент Provider и декоратор inject (работает как connect в редуксе).

15

mobx-react предлагает (экспериментальный - на момент написания) Provider (компонент) и inject (компонент более высокого порядка) для передачи свойств иерархии компонентов ниже.

Сверху вы можете использовать компонент Provider, чтобы передать всю необходимую информацию. Под капотом используется контекст 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') 
); 

В SomeComponent вы можете получить пройденные свойства, используя 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; 

[Отказ: Я об этом писал в MobX React: Simplified State Management in React, и вы можете увидеть minimal boilerplate application, который потребляет API SoundCloud.]