Redux: Documentos: Uso con React Router

Creado en 27 ago. 2015  ·  61Comentarios  ·  Fuente: reduxjs/redux

La gente tiene constantemente la impresión de que no admitimos React Router, o que necesita algo especial como redux-react-router para que funcione, o incluso que no funciona hasta React 0.14.

Puede usar Redux con React Router 0.13 o 1.0 tal como está hoy.
(Y eso fue cierto desde el lanzamiento inicial de Redux, por cierto).

Algunas características, como el viaje en el tiempo, deberán esperar algún soporte en el lado de RR, pero esto es irrelevante para el problema principal. La gente se confunde pensando que no pueden usar el enrutamiento hoy, lo cual es simplemente incorrecto.

El ejemplo del mundo real incluido en este repositorio usa React Router. Todo lo que necesita es envolver <Router> en <Provider> , tal como envolvería su componente de nivel superior en una aplicación sin enrutador.

Si desea realizar la transición desde los creadores de acciones, pase la instancia router como parámetro a los creadores de acciones y llame a sus métodos cuando lo desee. Si desea leer el estado del enrutador desde la tienda Redux, active una acción en el cambio de ruta y escriba un reductor para manejarlo. ¡Eso es todo!

react-redux-router es un experimento para tratar de crear una API más natural en la que simplemente envíe acciones y lea el estado de la tienda que automáticamente se conecta a la instancia del enrutador, pero no tiene que usarlo, o esperar a que se estabilice! Es solo un experimento.

Necesitamos esto en docs..

docs

Comentario más útil

Además, ¿cómo te sientes al agregar un ejemplo que muestre que el enrutador de reacción se usa universalmente?

Todos 61 comentarios

Originalmente había escrito el ejemplo de representación del lado del servidor para usar el enrutador de reacción en un contexto universal. Se sugirió que el uso con el enrutador de reacción podría convertirse en un documento propio. ¿Quizás podríamos agregar una sección a la documentación?

Además, ¿cómo te sientes al agregar un ejemplo que muestre que el enrutador de reacción se usa universalmente?

Creo que modificar el ejemplo real-world para que sea universal sería una buena idea. De esa manera no tienes que construir nada desde cero.

Si desea realizar la transición desde los creadores de acciones, pase la instancia del enrutador como parámetro a los creadores de acciones.

Solo quería aclarar que esto solo funciona en 0.13 ya que 1.0beta no tiene un concepto createRouter (¿todavía?), ¿correcto?

Puede hacerlo con 1.0.0-beta3 desde sus componentes de React.

class Thing extends Component {
  static contextTypes = {
    router: PropTypes.object
  }

  handleThing() {
    this.props.actionCreator(this.context.router);
  }
}

@timdorr ¿Es seguro usar this.context ? Tenía la impresión de que no estaba destinado a ser utilizado externamente.

Sí, es simplemente indocumentado, no inseguro. Se cambiará ligeramente en 0.14, pero no de una manera que rompa esto. Me imagino que será documentado en algún momento pronto.

@timdorr ¿Eso también significa que es posible hacer la transición a una URL diferente desde un creador de acciones en 1.0.0-beta3?

Sí, si pasa la instancia del enrutador al creador de la acción, puede hacer lo que quiera con ella, incluidas las transiciones.

Lancé esto:

const loginProps = {
  handleLogin: ({email, password}) => store.dispatch(userLogin({email, password})),
};



const routes = (
  <Route path="/" handler={App}>
    <Route path="login" handler={wrapper(Login, loginProps)} />
    <Route handler={authSection}>
      <Route path="" handler={Index} />
      <Route path="users" handler={wrapper(Users, (() => store.dispatch(getUsers())))} />
      <Route path="logout" handler={wrapper(Login, (() => store.dispatch(userLogout())))} />
    </Route>
  </Route>
);

const router = createRouter({
  location: HistoryLocation,
  routes,
});

store.dispatch(receiveRouter({router}));

Warning: Failed Context Types: Required context `store` was not specified in `SmartComponent(TodoApp)`. Check the render method of `Router`.

¿Qué puede estar mal?

PD: RR 1.0.0-beta3

@gyzerok Asegúrese de envolver todo el () => <Router>stuff</Router> en <Provider> , tal como lo hace el ejemplo real-world .

@gaearon sí, ofc. No lo envuelvo en el Proveedor, sino en mi propio componente, que es principalmente copiar y pegar de su Proveedor. La diferencia es que no le paso la tienda, sino que creo una tienda dentro de ella.

@gyzerok Es difícil decir qué está mal sin ver el código. (Y presente un problema por separado, react-redux repo es un buen lugar).

¡Gracias por el ejemplo de real-wolrd ! Pero, ¿cómo lidiar con AsyncProps ? Parece que la manipulación de doble contexto no funcionará en conjunto.

import React from 'react';                                                                                                                                                                                         
import {createStore} from 'redux';                                                                                                                                                                                 
import {Provider} from 'react-redux';                                                                                                                                                                              
import {Router, Route} from 'react-router';                                                                                                                                                                        
import BrowserHistory from 'react-router/lib/BrowserHistory';                                                                                                                                                      
import AsyncProps from 'react-router/lib/experimental/AsyncProps';                                                                                                                                                 

import App from './containers/App';                                                                                                                                                                                
import reducers from './reducers';                                                                                                                                                                                 

const store = createStoreWithMiddleware(reducers);                                                                                                                                                                 
const history = new BrowserHistory();                                                                                                                                                                               

React.render(                                                                                                                                                                                                       
    <Provider store={store}>                                                                                                                                                                                        
        {() =>                                                                                                                                                                                                      
            <Router history={history} createElement={AsyncProps.createElement}>                                                                                                                                     
                <Route component={AsyncProps}>                                                                                                                                                                      
                    <Route path="/" component={App} />                                                                                                                                                              
                </Route>                                                                                                                                                                                            
            </Router>                                                                                                                                                                                               
        }                                                                                                                                                                                                           
    </Provider>,                                                                                                                                                                                                    
    document.body                                                                                                                                                                                                  
);

y App.js

import React from 'react';                                                                                                                                                                                         
import {connect} from 'react-redux';                                                                                                                                                                               

let App = React.createClass({                                                                                                                                                                                      
    statics: {                                                                                                                                                                                                     
        loadProps(params, cb) {                                                                                                                                                                                    
            // have to call this with AsyncProps                                                                                                                                                                   
        }                                                                                                                                                                                                          
    },                                                                                                                                                                                                             
    displayName: 'App',                                                                                                                                                                                            

    render() {                                                                                                                                                                                                     
        return <div children="this is app" />                                                                                                                                                                      
    }                                                                                                                                                                                                              
});                                                                                                                                                                                                                

export default connect(state => state)(App); 

Funcionará sin el envoltorio connect , pero tampoco redux . Alguien ha enfrentado este problema?

¿O puede haber alguna otra forma de pausar la navegación hasta que se carguen los datos?

La estática es solo estática. Puede ponerlos en cualquier cosa, incluido el resultado connect() .

let App = React.createClass({                                                                                                                                                                                      
    displayName: 'App',                                                                                                                                                                                            

    render() {                                                                                                                                                                                                     
        return <div children="this is app" />                                                                                                                                                                      
    }                                                                                                                                                                                                              
});                                                                                                                                                                                                                

App = connect(state => state)(App); 

App.loadProps = function loadProps(params, cb) {                                                                                                                                                                                    
  // have to call this with AsyncProps                                                                                                                                                                   
}                                                                                                                                                                                                          

export default App; 

@gaearon lo siento, el ejemplo no está completo. Intenté extender el resultado connect con accesorios estáticos faltantes, pero un problema real con el contexto. Dame algo de tiempo, enviaré el ejemplo completo a un repositorio separado

Otra cosa que estoy investigando actualmente es almacenar parámetros en una tienda redux de una manera clara y discreta. Después de probar algunos enfoques, terminé escribiendo:

<Route
  component={OrderDetails}
  path='/orders/:orderId'
  onEnter={({params}) => store.dispatch(setCurrentOrder(params.orderId))} 
/>

por lo que es posible hacer referencia a los parámetros de los selectores, como a continuación:

export const OrderDetails = state => {
  const {order} = state;
  return {
    order: order.details.get(order.currentOrderId),
    orderId: order.currentOrderId,
    isLoading: order.isLoadingDetails,
    error: order.detailsLoadingError
  };
};

Probablemente cambiará una vez que se lance react-redux-router más estable.

Buenas noticias: React Router 1.0 RC ahora expone los ganchos que necesitamos.
Echa un vistazo a https://github.com/acdlite/redux-react-router y dinos si te gusta ahora.

@gaearon encontré el problema con react-router y AsyncProps experimental. Actualizar la reacción resuelve el problema.

@wtfil ¡Es bueno saberlo!

Al integrarme con react-router, entiendo por este hilo que podemos pasar la instancia del enrutador a un creador de acciones y llamar a métodos en él. Sin embargo, también entiendo que los creadores de acciones están destinados a no tener efectos secundarios en su forma más pura. El único caso que veo donde los creadores de acciones pueden tener efectos secundarios es cuando son acciones asincrónicas manejadas por middleware. ¿Se espera entonces que los creadores de acciones que realizan transiciones sean asincrónicos, en lugar de funciones puras?

¿Se espera entonces que los creadores de acciones que realizan transiciones sean asincrónicos, en lugar de funciones puras?

Los creadores de acciones pueden tener efectos secundarios. Es mejor evitarlos cuando sea posible pero, por supuesto, en algún momento los necesitarás, y dado que los reductores son puros en Redux, y no tenemos un mecanismo de efecto explícito como en Elm (ver #569 para la discusión), los creadores de acciones son los lugar para ponerlos.

Echa un vistazo a redux-router . Funciona sobre React Router, pero le permite enviar acciones y se encarga de sincronizar el enrutador.

esperando que React Router y Redux Router lleguen a 1.0...

Sí. Agregaremos una receta después de que esto suceda.

Así que he estado siguiendo esta discusión y también pensando un poco sobre cómo encaja el enrutamiento en redux en general (ver https://github.com/rackt/redux/issues/805). Basado en una discusión en ese hilo y algunos experimentos, encontré un enfoque que personalmente prefiero al pegamento react-router/react-redux-router.

Básicamente, trato de no dar a react-router o redux ningún conocimiento el uno del otro y, en cambio, los conecto a través de una implementación de historial personalizado. Con este enfoque, el enrutamiento se maneja así:

  1. Se crean una acción, un reductor y una clave en la tienda para route .
  2. Se crea y se escucha una implementación de historial estándar (este ejemplo usa el createHistory preferido pero podría usar fácilmente createHashHistory o lo que sea). Cuando se cambia la ubicación actual en el navegador, se envía una acción de RUTA, que finalmente coloca la ubicación en la tienda.
  3. Se crea una instancia de enrutador de reacción estándar con una segunda implementación de historial personalizada que notifica al suscriptor (enrutador de reacción) cuando se cambia la clave route de la tienda. También define createHref y pushState , delegando ambos al historial estándar creado en el paso 2.

Eso es todo. Creo que proporciona una separación de funciones bastante clara entre redux y react-router y elimina la necesidad de extraer otra biblioteca de "pegamento". Estoy pegando mi código a continuación, estaría muy interesado en escuchar cualquier comentario:

// please pay attention to library versions, this strategy is only tested with the indicated versions
import React from 'react'; // v0.13.3
import { Provider } from 'react-redux'; // v3.1.0
import { Router, Route, IndexRoute, Link } from 'react-router'; // v1.0.0-rc3
import { createHistory } from 'history'; // v1.12.3
import { createStore } from 'redux'; // v3.0.2

// define some components
class About extends React.Component {
    render () {
        return (
            <div><h1>About</h1></div>
        )
    }
}
class Home extends React.Component {
    render () {
        return (
            <div>
                <h1>Home</h1>
                <Link to="/about">Go to about</Link>
            </div>
        )
    }
}

// create a standard history object
var history = createHistory();

// set up 'route' action and action creator
const ROUTE = 'ROUTE';
function createRouteAction (location) {
    return {
        type: ROUTE,
        payload: location
    };
}

// set up reducer. here we only define behavior for the route action
function reducer (state = {}, action) {
    if (action.type === ROUTE) {
        return Object.assign({}, state, {
            route: action.payload
        });
    }
    else {
        return state;
        // whatever other logic you need
    }
}

// create store
const store = createStore(reducer);

// this factory returns a history implementation which reads the current state
// from the redux store and delegates push state to a different history.
function createStoreHistory () {
    return {
        listen: function (callback) {
            // subscribe to the redux store. when `route` changes, notify the listener
            const unsubscribe = store.subscribe(function () {
                const route = store.getState().route;
                callback(route);
            });

            return unsubscribe;
        },
        createHref: history.createHref,
        pushState: history.pushState
    }
}

React.render(
    <Provider store={store}>
        {() =>
            <Router history={createStoreHistory()}>
                <Route path="/about" component={About} />
                <Route path="/" component={Home} />
            </Router>
        }
    </Provider>,
    document.getElementById('root') // or whatever
);

// when the url changes, dispatch a route action. this is placed at the bottom so that the first route triggers the initial render
const unlisten = history.listen(function (location) {
    store.dispatch(createRouteAction(location));
});

Menciono esto, por cierto, porque creo que este ejemplo podría ayudar a algunas personas como yo que estaban luchando con el uso de react-router con redux y ayudar a aclarar su afirmación de que redux y react-router se pueden usar juntos hoy.

@cappslock Me gusta, aunque hay una pequeña cosa. En su implementación, cambiar la ruta es la acción, esto rompe el enfoque de "La acción es un evento, no un comando", lo que eventualmente puede conducir a algunas prácticas desagradables. Invirtamos el pensamiento, potencialmente cualquier acción puede resultar en un efecto secundario que cambia la barra de direcciones (ya sea un componente o la barra de direcciones del navegador real...) y el efecto secundario resulta en el envío de una nueva acción ( ROUTE_CHANGED ). Es básicamente el mismo patrón como activar una llamada API.

@tomkis1 gracias por los comentarios. Si te entiendo correctamente, esto en realidad ya funciona de esa manera. La acción ROUTE se envía como un efecto secundario del cambio de URL. ¿Tal vez ROUTE_CHANGED sería un mejor nombre?

¡Oh! Acabo de comprobar dos veces y tenías razón. Sí ROUTE_CHANGED tendría más sentido.

Yo también lo creo. Volvería y lo cambiaría, pero estos comentarios serían realmente confusos :)

El flujo es así, para aclarar:

Cambio de URL -> acción ROUTE (o ROUTE_CHANGED ) -> tienda de actualizaciones del reductor -> historial de la tienda (previamente suscrito a la tienda) notifica a los oyentes -> actualizaciones del enrutador de reacción

Prefiero esto porque no quiero nada más que la tienda que controla el estado de la interfaz de usuario observado por el usuario. También parece preferible para las pruebas.

Una deficiencia de este enfoque es que la URL no se actualiza en respuesta a la acción ROUTE_CHANGED . Ni siquiera estoy seguro de si esto es deseable si decimos que no queremos que las acciones se traten como comandos, pero imagino que podría completarse como un efecto secundario del creador de acciones ROUTE_CHANGED o por un suscriptor de tienda independiente.

Por cierto, si esta discusión está más allá del alcance de este problema, házmelo saber y lo moveré.

@cappslock ¡Me gusta mucho! Definitivamente no creo que sea un problema que despachar ROUTE_CHANGED no cambie la ruta. Tratar la acción como un evento y no como un activador me parece más claro/más comprensible (ya que responde a una interacción del usuario; no esperaría que una acción BUTTON_CLICKED active un clic en un botón). Sin embargo, hay una parte de tu código que no entiendo. ¿Puedes darme más detalles sobre esta parte?

esto se coloca en la parte inferior para que la primera ruta active el renderizado inicial

@elliotdickison ¡Gracias! Intentaré aclarar eso, pero tome lo que digo con pinzas, ya que se basa en prueba y error y suposiciones en lugar de un análisis profundo. Esto es más una prueba de concepto/boceto en este punto.

Cuando coloqué ese código sobre la creación de instancias de ReactRouter , el componente correspondiente a la ruta / no se representó. El enrutador aún funcionaría si las acciones se enviaran manualmente o el estado se empujara manualmente, por lo que pensé que era un problema del ciclo de vida con el historial. Moviéndolo debajo de la creación de instancias de ReactRouter resolvió esto. Sospecho que la biblioteca de historial difiere la notificación de la ruta inicial hasta que haya al menos un suscriptor. Si ese suscriptor se configura antes que ReactRouter, ninguna de las notificaciones lo alcanza.

Al intentar explicar esto, me doy cuenta de que mi comprensión es un poco deficiente; Estudiaré esto más y trataré de dar una mejor respuesta.

Una deficiencia de este enfoque es que la URL no se actualiza en respuesta a la acción ROUTE_CHANGED. Ni siquiera estoy seguro de si esto es deseable si decimos que no queremos que las acciones se traten como comandos, pero imagino que podría completarse como un efecto secundario del creador de la acción ROUTE_CHANGED o por un suscriptor de tienda independiente.

Diría que esto es lo deseado, ROUTE_CHANGED definitivamente debería ser activado por una fuente externa (por ejemplo, onhashchange...) El cambio de URL de la OMI debería dar como resultado ROUTE_CHANGED no al revés.

Estoy de acuerdo. Pensé que sería bueno tener algo que se suscribiera a la tienda y mantuviera la URL sincronizada en caso de que una acción de ROUTE_CHANGED enviada se originara a partir del código y no de un evento de historial real, pero se podría argumentar que ese caso representa un error de programación

@cappslock tu enfoque es realmente muy bueno. ¿Podrías hacer alguna entrada de blog al respecto?

@vojtatranta Solo echa un vistazo a https://github.com/rackt/redux/issues/805 Supongo que fue la inspiración detrás de la implementación.

@vojtatranta Gracias! No tengo un blog, así que casi toda la información que tengo está en este hilo y en el #805. ¿Algo en particular sobre lo que quisieras más información?

1.0 está fuera.

Es tiempo de:

  • Ejemplo de enrutamiento de puertos para usarlo
  • Agregue la receta "Uso con enrutador" basada en el ejemplo real-world

:aplaudir:

@gaearon , ¿puede hacer referencia a este problema cuando el ejemplo de _Uso con enrutador_ está en un PR? Mucha gente que conozco (incluido yo mismo) está buscando aclaraciones sobre cómo estos dos funcionan bien juntos.

Si seguro. Aquí es cuando el problema se cerrará. :-)

¿Quizás redux-simple-router debería considerarse ahora?

redux-enrutador-simple +1

Acabo de convertir el ejemplo universal + react-router (+redux-simple-router)
https://github.com/eriknyk/redux-universal-app

hola a todos, ¿cuál es la conclusión de esta discusión? veo que los documentos no están actualizados para su uso con react-router
cc @gaearon

@gaearon después de vincular mi aplicación de reacción a redux, solo uso el estado para controlar la visualización/ocultación de mis componentes. Así que no creo que la función original de "enrutador" como RR se ajuste a mi aplicación ahora.
Lo único que creo que debe hacer el "nuevo enrutador" es asignar la URL al estado (¿a través de acciones?) Y volver a asignar el estado a la URL también.
Si dejamos que url decida cómo se muestra el componente de la aplicación (tal vez algunos de ellos), eso significa que tenemos dos fuentes de estado, una es la url, la otra es la tienda de redux, eso hará las cosas más difíciles...
¿Qué dices acerca de esto? ¿Deberíamos dejar que la barra de direcciones sea otro componente de nuestra aplicación?

Gracias

Me comprometo oficialmente a escribir este documento después de que enviemos https://github.com/rackt/react-router-redux/pull/259. Esta será la forma bendecida de vincular React Router y Redux. En el documento, primero mostraremos cómo usarlos sin ese paquete y, gradualmente, presentaremos dos ventajas que ofrece el paquete: middleware y mover la fuente de la verdad de enrutamiento a la tienda. Ambos son opcionales, por lo que nos aseguraremos de explicarle en qué caso desea optar por usarlos y qué le brindan en lugar de vanilla RR.

Aquí hay un pensamiento a considerar: si algo de lo que explica sobre el middleware relacionado con el enrutamiento y el historial también podría convertirse en una parte específica de la aplicación realista dentro de la explicación general de http://rackt.org/redux/docs/advanced/Middleware.html ( por ejemplo, en los ejemplos al final)

@gaearon ¿ algún progreso en el documento de React Router/próximos pasos? Estoy leyendo la documentación de Redux y me encanta, pero estoy desanimado por esos enlaces falsos :(

Recién comencé a reescribir los documentos del enrutador de reacción en mi repositorio personal y planeo tener la sección redux allí también. Podría tener algo hecho pronto, depende de mi tiempo libre. Te mantendré informado. https://github.com/knowbody/react-router-docs

Para ser justos, no necesita nada en el lado de Redux para que el enrutador de reacción funcione. Pero sí, tenemos que hacer esto.

Atención: React Router 3.0 funcionará mejor con las optimizaciones de React Redux connect() , y el nuevo withRouter() HOC significa que no necesita usar el contexto directamente.

https://twitter.com/dan_abramov/status/729768048417251328

@gaearon , @timdorr , ¿podría aclarar las compensaciones entre pasar una instancia de enrutador como argumento a los creadores de acciones e importar directamente el historial del navegador en los creadores de acciones (como se sugiere aquí https://github.com/reactjs/react-router/blob )

router simplemente envuelve su instancia de historial con algunas ventajas adicionales, pero son los mismos métodos push y replace entre las dos instancias.

Gracias, @timdorr.

Supongo que la pregunta entonces es ¿por qué necesitamos la composición withRouter() si router básicamente envuelve el singleton histórico (es un singleton, verdad)?

¿Es para permitir un acoplamiento más flexible entre un componente y una instancia de historial (es decir, para evitar que el componente acceda directamente a un objeto único)? Si es así, ¿no se aplicaría la misma lógica al acceder a la instancia de historial desde un creador de acciones?

Sí, y si proporciona su propia instancia de historial y no quiere (o no puede) crear su propio módulo singleton (lo que puede ser confuso si no está muy familiarizado con los sistemas de módulos JS). Si quieres hacerlo tú mismo, eres más que bienvenido a seguir nuestro patrón.

No estoy seguro de si valdría la pena documentar withRouter sobre cómo se puede usar para múltiples componentes de orden superior. Todavía estoy tratando de averiguar la mejor manera de evitar esto:
connect(mapStateToProps, mapDispatchToProps)(withRouter(withAnalytics(withLanguage(TestForm)))); .

También podría usar algo como compose ?

const enhance = compose(
  connect(mapStateToProps, mapDispatchToProps),
  withRouter,
  withAnalytics,
  withLanguage
);

export default enhance(TestForm);

Sin embargo, en mi caso de uso, el contexto tendrá un usuario conectado, idioma actual, información del tema y análisis, lo que hace que esto sea un desafío con mucho contexto y muchos componentes conectados.

Otra idea es duplicar la lógica withRouter y connect en un espacio de nombres de contexto: withAppContext() => props.app = { user, lang, theme, analytics, router, connect? } ?

¿Sería esto beneficioso para la documentación o un ejemplo de uso withRouter con connect ?

@gaearon , ¿hay alguna actualización para esto ahora que React Router 3.0.0 y sus nuevos videos de Egghead han estado disponibles por un tiempo, y este hilo se abrió hace un año?

Hecho en #1929

¿Fue útil esta página
0 / 5 - 0 calificaciones