javascript - decorators - ¿Cómo conectar el estado a los accesorios con mobx.js @observer cuando se usa la clase ES6?
mobx-react (4)
Tomemos una clase como esta en una aplicación con React y React Router.
@observer class Module1 extends React.Component {
constructor (props) {
super(props);
//...
}
componentWillMount(){
//...
}
method(){
//...
}
otherMethod(){
//...
}
render() {
return (
<ChildComp bars={this.props.bars}/>}
);
}
}
Y tomemos un estado como este
state = observable({
module1:{
bars:{
//...
}
},
module2:{
foos:{
//...
}
}
})
El componente Module1 se carga así:
//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'')
);
¿Cómo podría pasar el props module1.bars
al componente Module1? En redux usaría <provider>
y redux-connect
pero estoy un poco perdido con esto en Mobx.js.
Echa un vistazo a react-tunnel . Le proporciona un componente de Provider
y el decorador de inject
(funciona como connect
en redux).
Hace una semana comenzamos un nuevo proyecto con reac y mobx , y enfrenté el mismo problema que el tuyo. Después de mirar alrededor, encontré que la mejor manera es usar el contexto de reaccion. Así es cómo:
La tienda: 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: estamos usando Singleton para asegurarnos de que sea solo una instancia, ya que la tienda se puede usar fuera de los componentes de reacción, por ejemplo. routes.js
Las rutas: 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>
);
El componente 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;
}
}
Y finalmente, un componente que usa la tienda: 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>
);
}
}
Puede declarar nuevas tiendas y agregarlas en getChildContext
de App.js
, y cada vez que necesite una tienda determinada, simplemente declare la dependencia de la store
en los contextTypes
de contextTypes
del componente, y this.context
de this.context
.
Noté que no es un requisito pasar un observable como prop, solo con tener el decorador @observer
y usar cualquier valor observable en su componente, mobx
y mobx-react
hacen su magia.
Por cierto, <Provider store={myStore}><App /></Provider>
redux hace lo mismo que se explica en App.js
https://egghead.io/lessons/javascript-redux-passing-the-store-down-implicitly-via-context
Referencia:
Primero, aquí hay una aplicación de ejemplo simple que realiza enrutamiento utilizando MobX, React y react-router: https://github.com/contacts-mvc/mobx-react-typescript
En general, personalmente me gusta pasar explícitamente todas las tiendas relevantes como accesorios explícitos a mis componentes. Pero también puede usar un paquete como Ryan para que sus tiendas pasen a sus componentes utilizando el mecanismo de contexto React, similar a Redux connect (consulte esta app para ver un ejemplo).
Una vez que tenga su tienda en su componente, analice los parámetros de enrutamiento en ComponentWillMount y actualice sus tiendas en consecuencia.
Básicamente, eso debería ser todo :) Pero avísame si dejo algo sin respuesta.
mobx-react ofrece un Provider
(componente) experimental (al momento de escribir esto) e inject
(componente de orden superior) para pasar las propiedades a la jerarquía de componentes a continuación.
Desde arriba puede usar el componente Provider
para pasar toda la información relevante. Bajo el capó se utiliza el contexto 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'')
);
En SomeComponent
puede recuperar las propiedades pasadas usando el HOC de inject
:
import { observer, inject } from ''mobx-react'';
...
const SomeComponent = inject(''oneStore'', ''anotherStore'')(observer(({ oneStore, anotherStore }) => {
return <div>{oneStore.someProp}{anotherStore.someOtherProp}</div>;
}))
export default SomeComponent;
[Descargo de responsabilidad: escribí sobre esto en MobX React: Simplified State Management en React y puede ver una aplicación de boilerplate mínima que consume la API de SoundCloud.]