Redux: Documentos: Uso com React Router

Criado em 27 ago. 2015  ·  61Comentários  ·  Fonte: reduxjs/redux

As pessoas constantemente têm a impressão de que não suportamos o React Router, ou que você precisa de algo especial como redux-react-router para que funcione, ou mesmo que não funcione até o React 0.14.

Você pode usar o Redux com o React Router 0.13 ou 1.0 como é hoje.
(E isso era verdade desde o lançamento inicial do Redux, a propósito.)

Alguns recursos, como viagem no tempo, precisarão aguardar algum suporte do lado RR, mas isso é irrelevante para o problema principal. As pessoas ficam confusas pensando que não podem usar roteamento hoje, o que é simplesmente errado.

O exemplo do mundo real incluído neste repositório usa o React Router. Tudo que você precisa é envolver <Router> em <Provider> , assim como você envolveria seu componente de nível superior em um aplicativo sem roteador.

Se você quiser fazer a transição de criadores de ação, passe a instância router como um parâmetro para criadores de ação e chame seus métodos quando quiser. Se você quiser ler o estado do roteador do armazenamento Redux, dispare uma ação na mudança de rota e escreva um redutor para lidar com isso. É isso!

react-redux-router é um experimento na tentativa de criar uma API mais natural, onde você apenas despacharia ações e leria o estado do armazenamento que está conectado automaticamente à instância do roteador, mas você não precisa usá-lo, ou esperar que se estabilize! É apenas um experimento.

Precisamos disso em documentos ..

docs

Comentários muito úteis

Além disso, o que você acha de adicionar um exemplo que mostra o roteador de reação sendo usado universalmente?

Todos 61 comentários

Eu originalmente escrevi o exemplo de renderização do lado do servidor para usar o roteador de reação em um contexto universal. Foi sugerido que o uso com o roteador react poderia se tornar um documento próprio. Talvez possamos adicionar uma seção à documentação?

Além disso, o que você acha de adicionar um exemplo que mostra o roteador de reação sendo usado universalmente?

Acho que modificar o exemplo real-world para ser universal seria uma boa ideia. Dessa forma, você não precisa construir nada do zero.

Se você quiser fazer a transição dos criadores de ação, passe a instância do roteador como parâmetro para os criadores de ação

Só queria esclarecer, isso só funciona em 0.13 já que 1.0beta não tem um conceito createRouter (ainda?), correto?

Você pode fazer isso com 1.0.0-beta3 de dentro de seus componentes React.

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

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

@timdorr É seguro usar this.context ? Fiquei com a impressão de que não era para ser usado externamente.

Sim, é apenas indocumentado, não inseguro. Está sendo ligeiramente alterado em 0,14, mas não de uma forma que quebraria isso. Imagino que será documentado em algum momento em breve.

@timdorr Isso também significa que é possível fazer a transição para um URL diferente de um criador de ação em 1.0.0-beta3?

Sim, se você passar a instância do roteador para o criador da ação, poderá fazer o que quiser com ela, incluindo transições.

Juntei isso:

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`.

O que pode estar errado?

PS: RR 1.0.0-beta3

@gyzerok Certifique-se de envolver todo o () => <Router>stuff</Router> em <Provider> , assim como o exemplo real-world faz.

@gaearon sim, ofc. Eu o envolvo não no provedor, mas no meu próprio componente, que é principalmente copiar e colar do seu provedor. A diferença é que eu não passo store para ele, mas crio store dentro dele.

@gyzerok É difícil dizer o que está errado sem ver o código. (E, por favor, registre um problema separado, react-redux repo é um bom lugar.)

Obrigado por real-wolrd exemplo! Mas como lidar com AsyncProps ? Parece que a manipulação de contexto duplo não funcionará em 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                                                                                                                                                                                                  
);

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

Ele funcionará sem connect wrapper, mas também não haverá redux . Alguém já enfrentou este problema?

Ou pode haver outra maneira de pausar a navegação até que os dados sejam carregados?

A estática é apenas estática. Você pode colocá-los em qualquer coisa, incluindo o 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 desculpe, o exemplo não está completo. Eu tentei estender o resultado connect com adereços estáticos ausentes, mas um problema real com o contexto. Dê-me algum tempo, vou colocar o exemplo inteiro em um repositório separado

Outra coisa que estou investigando atualmente é armazenar parâmetros em uma loja redux de maneira clara e discreta. Depois de tentar algumas abordagens, acabei escrevendo:

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

então é possível fazer referência a parâmetros de seletores, como abaixo:

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

Provavelmente mudará quando mais estável react-redux-router for lançado.

Boas notícias: o React Router 1.0 RC agora expõe os ganchos que precisamos.
Confira https://github.com/acdlite/redux-react-router e deixe-nos saber se você gosta agora!

@gaearon eu encontrei o problema com react-router e experimental AsyncProps . Atualizar o react resolve o problema

@wtfil Bom saber!

Ao integrar com o react-router, entendo neste tópico que podemos passar a instância do roteador para um criador de ação e chamar métodos nele. No entanto, também entendo que os criadores de ação não devem ter efeitos colaterais em sua forma mais pura. O único caso que vejo em que os criadores de ação podem ter efeitos colaterais é quando são ações assíncronas tratadas pelo middleware. A expectativa, então, é que os criadores de ação que realizam transições sejam assíncronos, em vez de funções puras?

A expectativa, então, é que os criadores de ação que realizam transições sejam assíncronos, em vez de funções puras?

Os criadores de ação podem ter efeitos colaterais. É melhor evitá-los quando possível, mas é claro que em algum momento você precisa deles, e como os redutores são puros no Redux, e não temos um mecanismo de efeito explícito como no Elm (veja #569 para discussão), os criadores de ação são o lugar para colocá-los.

Confira redux-router . Ele funciona em cima do React Router, mas permite que você despache ações e cuide da sincronização do roteador.

aguardando o React Router e o Redux Router atingirem 1.0 ...

Sim. Adicionaremos uma receita depois que isso acontecer.

Então, tenho acompanhado essa discussão e também pensado um pouco sobre como o roteamento se encaixa no redux em geral (consulte https://github.com/rackt/redux/issues/805). Com base em algumas discussões nesse tópico e em algumas experiências, encontrei uma abordagem que pessoalmente prefiro à cola react-router/react-redux-router.

Basicamente, tento não dar ao react-router ou redux nenhum conhecimento um do outro e, em vez disso, conectá-los por meio de uma implementação de histórico personalizada. Com essa abordagem, o roteamento é tratado assim:

  1. Uma ação, um redutor e uma chave na loja são criados para route .
  2. Uma implementação de histórico padrão (este exemplo usa o createHistory preferencial, mas você pode facilmente usar createHashHistory ou qualquer outro) é criada e ouvida. Quando a localização atual no navegador é alterada, uma ação ROUTE é despachada, colocando a localização na loja.
  3. Uma instância padrão de roteador de reação é criada com uma segunda implementação de histórico personalizado que notifica o assinante (roteador de reação) quando a chave route do armazenamento é alterada. Também define createHref e pushState , delegando ambos ao histórico padrão criado na etapa 2.

É isso. Eu acho que fornece uma separação bastante limpa de tarefas entre redux e react-router e remove a necessidade de puxar outra biblioteca de "cola". Estou colando meu código abaixo, estou muito interessado em ouvir algum feedback:

// 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 isso, a propósito, porque acho que este exemplo pode ajudar algumas pessoas como eu que estavam lutando com o uso de react-router com redux e ajudar a esclarecer sua afirmação de que redux e react-router podem ser usados ​​juntos hoje.

@cappslock Eu gosto, mas há uma coisinha. Na sua implementação, mudar a rota é a ação, isso meio que quebra a abordagem "Ação é um evento, não um comando" o que pode eventualmente levar a algumas práticas desagradáveis. Vamos inverter o pensamento, potencialmente qualquer ação pode resultar em efeito colateral que altera a barra de endereços (seja um componente ou a barra de endereços do navegador real....) e o efeito colateral resulta no envio de uma nova ação ( ROUTE_CHANGED ). É basicamente o mesmo padrão como acionar uma chamada de API.

@tomkis1 obrigado pelo feedback. Se eu entendi corretamente, isso na verdade já funciona dessa maneira. A ação ROUTE é despachada como um efeito colateral da alteração do URL. Talvez ROUTE_CHANGED seria um nome melhor?

Oh! Acabei de verificar e você estava certo. Sim ROUTE_CHANGED faria mais sentido.

Eu também acho. Eu voltaria e mudaria, mas esses comentários seriam muito confusos :)

O fluxo é assim, para esclarecer:

Alteração de URL -> ROUTE (ou ROUTE_CHANGED ) ação -> redutor atualiza loja -> histórico da loja (anteriormente inscrito na loja) notifica os ouvintes -> atualizações do roteador de reação

Prefiro isso porque não quero nada além da loja que conduz o estado observado pelo usuário da interface do usuário. Também parece preferível para testes.

Uma desvantagem dessa abordagem é que a URL não é atualizada em resposta à ação ROUTE_CHANGED . Não tenho certeza se isso é desejável se estamos dizendo que não queremos que as ações sejam tratadas como comandos, mas imagino que isso possa ser concluído como um efeito colateral do criador de ações ROUTE_CHANGED ou por um assinante de loja separado.

BTW, se esta discussão estiver além do escopo desta questão, por favor me avise e eu a moverei.

@cappslock eu gosto muito disso! Eu definitivamente não acho que seja um problema que despachar ROUTE_CHANGED não mude a rota. Tratar a ação como um evento e não um gatilho me parece mais limpo/compreensível (já que está respondendo a uma interação do usuário; você não esperaria que uma ação BUTTON_CLICKED realmente acionasse um clique em um botão). Há uma parte do seu código que eu não entendo. Você pode detalhar essa parte para mim?

isso é colocado na parte inferior para que a primeira rota acione a renderização inicial

@elliotdickison Obrigado! Vou tentar esclarecer isso, mas, por favor, tome o que digo com um grão de sal, pois é baseado em tentativa e erro e suposições, e não em análise profunda. Esta é mais uma prova de conceito/esboço neste momento.

Quando coloquei esse código acima da instanciação de ReactRouter , o componente correspondente à rota / não foi renderizado. O roteador ainda funcionaria se as ações fossem despachadas manualmente ou o estado fosse enviado manualmente, então imaginei que era um problema de ciclo de vida com o histórico. Movendo-o abaixo da instanciação de ReactRouter resolveu isso. Suspeito que a biblioteca de histórico adie a notificação da rota inicial até que haja pelo menos um assinante. Se esse assinante for configurado antes do ReactRouter, nenhuma das notificações o alcançará.

Tentando explicar isso, percebo que minha compreensão é um pouco deficiente; Vou pesquisar mais sobre isso e tentar dar uma resposta melhor.

Uma desvantagem dessa abordagem é que a URL não é atualizada em resposta à ação ROUTE_CHANGED. Não tenho certeza se isso é desejável se estamos dizendo que não queremos que as ações sejam tratadas como comandos, mas imagino que isso possa ser concluído como um efeito colateral do criador da ação ROUTE_CHANGED ou por um assinante de loja separado.

Eu diria que isso é desejado, ROUTE_CHANGED definitivamente deve ser disparado por fonte externa (por exemplo, onhashchange...) A mudança de URL IMO deve resultar em ROUTE_CHANGED não vice-versa.

Eu meio que concordo. Achei que seria bom ter algo assinando a loja e mantendo a URL sincronizada no caso de uma ação ROUTE_CHANGED despachada originada do código e não de um evento histórico real, mas você pode argumentar que esse caso representa um erro de programação.

@cappslock sua abordagem é realmente muito boa. Você poderia fazer algum blogpost sobre isso?

@vojtatranta Basta conferir https://github.com/rackt/redux/issues/805 Acho que foi a inspiração por trás da implementação.

@vojtatranta Obrigado! Eu não tenho um blog, então praticamente todas as informações que tenho estão neste tópico e #805. Alguma coisa em particular que você queria mais informações?

1.0 está fora.

É hora de:

  • Exemplo de roteamento de porta para usá-lo
  • Adicione a receita “Uso com roteador” com base no exemplo real-world

:aplaudir:

@gaearon você pode fazer referência a esse problema quando o exemplo _Usage with Router_ está em um PR? Muitas pessoas que conheço (inclusive eu) estão procurando esclarecimentos sobre como esses dois funcionam bem juntos.

Sim claro. É quando o assunto será encerrado. :-)

Talvez redux-simple-router deva ser considerado agora?

redux-simple-router +1

Acabei de converter o exemplo universal + react-router (+ redux-simple-router)
https://github.com/eriknyk/redux-universal-app

oi tudo, qual é a conclusão desta discussão? vejo que os documentos não estão atualizados para uso com roteador de reação
cc @gaearon

@gaearon depois de vincular meu aplicativo react ao redux, apenas uso o estado para controlar a exibição/ocultação dos meus componentes. Portanto, não acho que a função original de "roteador" como RR se encaixa no meu aplicativo agora.
A única coisa que eu acho que o "novo roteador" precisa fazer é mapear o URL para o estado (via ações?) e remapear o estado de volta para o URL também.
Se deixarmos a url decidir como o componente da aplicação (talvez alguns deles) será exibido, isso significa que temos duas fontes de estado, uma é a url, a outra é a loja do redux, o que tornará as coisas mais difíceis...
O que você diz sobre isso? devemos deixar a barra de endereços ser outro componente da nossa aplicação.

Obrigado

Comprometo-me oficialmente a escrever este documento depois de enviarmos https://github.com/rackt/react-router-redux/pull/259. Esta será a maneira abençoada de vincular o React Router e o Redux. No documento, vamos primeiro mostrar como usá-los sem esse pacote e, gradualmente, introduzir duas conveniências que o pacote oferece: middleware e mover a fonte de roteamento da verdade para a loja. Ambos são opcionais, portanto, explicaremos em qual caso você deseja optar por usá-los e o que eles oferecem a você em relação ao vanilla RR.

Aqui está um pensamento a ser considerado: se alguma coisa que você explica sobre middleware relacionado a roteamento e histórico também pode se tornar um pedaço específico de aplicação realista dentro da explicação geral de http://rackt.org/redux/docs/advanced/Middleware.html ( por exemplo, nos exemplos no final)

@gaearon algum progresso no documento do React Router / próximos passos? Estou lendo a documentação do Redux e adorando, mas estou chateado com esses links falsos :(

Acabei de começar a reescrever os documentos do roteador react no meu repositório pessoal e estou planejando ter a seção redux lá também. Eu posso ter algo feito em breve, depende do meu tempo livre. Eu vou manter você informado. https://github.com/knowbody/react-router-docs

Para ser justo, você não precisa de nada no lado do Redux para que o roteador de reação funcione. Mas sim, precisamos fazer isso.

Atenção: o React Router 3.0 funcionará melhor com as otimizações do React Redux connect() , e o novo withRouter() HOC significa que você não precisa usar o contexto diretamente.

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

@gaearon , @timdorr você poderia esclarecer as vantagens e desvantagens entre passar uma instância do roteador como um argumento para os criadores de ação e importar diretamente o browserHistory nos criadores de ação (como sugerido aqui https://github.com/reactjs/react-router/blob /master/docs/guides/NavigatingOutsideOfComponents.md?

router apenas envolve sua instância de histórico com alguns extras, mas são os mesmos métodos push e replace entre as duas instâncias.

Obrigado, @timdorr.

Eu acho que a questão então se torna por que precisamos da composição withRouter() se router basicamente envolve o singleton histórico (é um singleton, certo)?

É para permitir um acoplamento mais flexível entre um componente e uma instância do histórico (ou seja, para evitar que o componente acesse diretamente um objeto singleton)? Em caso afirmativo, a mesma lógica não se aplicaria ao acessar a instância do histórico de um criador de ação?

Sim, e se você fornecer sua própria instância de histórico e não quiser (ou não puder) criar seu próprio módulo singleton (o que pode ser confuso se você não estiver super familiarizado com sistemas de módulos JS). Se você quiser fazer isso sozinho, é mais do que bem-vindo a seguir nosso padrão.

Não tenho certeza se valeria a pena documentar withRouter sobre como ele pode ser usado para vários componentes de ordem superior. Ainda estou tentando descobrir a melhor maneira de evitar isso:
connect(mapStateToProps, mapDispatchToProps)(withRouter(withAnalytics(withLanguage(TestForm)))); .

Eu também poderia usar algo como compose ?

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

export default enhance(TestForm);

No entanto, no meu caso de uso, o contexto terá usuário logado, idioma atual, informações de tema e análises, o que torna isso desafiador com muito contexto e muitos componentes conectados.

Outra ideia é duplicar a lógica withRouter e connect em um namespace de contexto: withAppContext() => props.app = { user, lang, theme, analytics, router, connect? } ?

Isso seria benéfico para a documentação ou um exemplo de uso de withRouter com connect ?

@gaearon há alguma atualização para isso agora que o React Router 3.0.0 e seus novos vídeos Egghead foram lançados por um tempo, e este tópico foi aberto por um ano?

Feito em #1929

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

vraa picture vraa  ·  3Comentários

vslinko picture vslinko  ·  3Comentários

cloudfroster picture cloudfroster  ·  3Comentários

ramakay picture ramakay  ·  3Comentários

rui-ktei picture rui-ktei  ·  3Comentários