by Michael Sargent

uma biblioteca de roteamento é um componente chave de qualquer aplicação complexa de uma única página. Se você desenvolver aplicativos web com React e Redux, você provavelmente usou, ou pelo menos ouviu falar de Router Reat. É uma biblioteca de roteamento bem conhecida para Reat, e uma grande solução para muitos casos de uso.

mas Reat Router não é a única solução viável no ecossistema React/Redux. Na verdade, há toneladas de soluções de roteamento construídas para Redux E Redux, cada uma com APIs, características e objetivos diferentes — e a lista está apenas crescendo. Escusado será dizer que o roteamento do lado do cliente não vai desaparecer tão cedo, e ainda há muito espaço para o design nas bibliotecas de roteamento de amanhã.

Hoje, eu quero chamar a sua atenção para o assunto de roteamento em Redux. Vou apresentar e fazer um caso para Redux – primeiro roteamento-um paradigma que faz Redux a estrela do modelo de roteamento, e o fio comum entre muitas soluções de roteamento Redux. Vou demonstrar como montar o núcleo, framework-agnostic API em menos de 100 linhas de código, antes de explorar opções para o uso do mundo real com Reat e outros frameworks front-end.

Um Pouco de História

No navegador, a localização (URL) e o histórico da sessão (uma pilha de locais visitados pelo navegador atual guia) são armazenados no global window objeto. São acessíveis via:

  • window.location (API de localização)
  • window.history (History API).

A API de Histórico oferece a seguinte história com os métodos de navegação, notáveis por sua capacidade de atualizar o histórico do navegador e localização sem a necessidade de uma recarregar a página:

  • pushState(href) — empurra um novo local para a pilha de histórico
  • replaceState(href) — substitui o local atual na pilha
  • back() — navega para o local anterior na pilha
  • forward() — navega para o próximo local na pilha
  • go(index) — navega para um local na pilha, em qualquer direção.Juntos, as APIs de História e localização permitem o paradigma de roteamento do cliente moderno conhecido como roteamento do pushState — o primeiro protagonista da nossa história.

    agora, é quase um crime mencionar a história e localização APIs sem mencionar uma biblioteca wrapper moderna como history.

    readaptação / história
    gerir a história da sessão com JavaScriptgithub.com

    history fornece uma API simples mas poderosa para interagir com o histórico e localização do navegador, enquanto cobre inconsistências entre diferentes implementações do navegador. Ele é usado como um par ou dependência interna em muitas bibliotecas modernas de roteamento, e eu vou fazer várias referências a ele ao longo deste artigo.

    Redux and pushState Routing

    o segundo protagonista da nossa história é Redux. Estamos em 2017, por isso vou poupá-lo à introdução e vou direto ao ponto:

    usando roteamento pushState simples em uma aplicação Redux, dividimos o estado da aplicação em dois domínios: histórico do navegador e a Loja Redux.

    eis o que se parece com o Router React, que instancia e enrola history:

    history → React Router ↘ view Redux ↗

    agora, sabemos que nem todos os dados têm que residir na loja. Por exemplo, o estado do componente local é muitas vezes um lugar adequado para armazenar dados que são específicos de um único componente.

    mas os dados de localização não são triviais. É uma parte dinâmica e importante do Estado de aplicação — o tipo de dados que pertence à loja. Mantê-lo na loja permite Redux luxos como a depuração de viagens no tempo, e fácil acesso a partir de qualquer componente conectado à loja.Então, como vamos mudar o local para a loja?

    não Há como contornar o fato de que o navegador lê e armazena história e localização de informações em window, mas o que podemos fazer é manter uma cópia dos dados de localização da loja, e mantê-lo sincronizado com o navegador.

    não é isso que react-router-redux faz para o Router React?

    Yes, but only to enable the time-travel capabilities of the Redux DevTools. A aplicação ainda depende dos dados de localização mantidos no Router React:

    history → React Router ↘ ↕ view Redux ↗

    usando react-router-redux para ler os dados de localização Da Loja em vez de Router React é desencorajado (devido a fontes potencialmente conflitantes de verdade).Podemos fazer melhor?

    podemos construir um modelo de roteamento alternativo-um que é construído a partir do zero para jogar bem com Redux, permitindo — nos ler e atualizar a localização da maneira Redux-com store.getState() e store.dispatch()?

    podemos absolutamente, e é chamado Redux-primeiro roteamento.

    Redux-primeiro encaminhamento

    Redux-first routing é uma variação do routing pushState que faz Redux a estrela do modelo de routing.

    uma solução de encaminhamento Redux-first satisfaz os seguintes critérios:

    • a localização é realizada na loja Redux.
    • a localização é alterada através da expedição de acções de Redux.
    • a aplicação lê os dados de localização apenas a partir da loja.
    • o histórico da loja e do navegador são mantidos em sincronia nos bastidores.

    aqui está uma ideia básica de como isso se parece:Não existem ainda duas fontes de dados de localização?

    Sim, mas se pudermos confiar que o histórico do navegador e a Loja Redux estão em sincronia, podemos construir nossas aplicações para apenas ler os dados de localização da loja. Então, do ponto de vista da aplicação, há apenas uma fonte de verdade — a loja.

    como realizamos o Redux-primeiro encaminhamento?

    podemos começar por criar um modelo conceptual, fundindo os elementos fundamentais dos modelos de roteamento do lado do cliente e Redux do ciclo de vida dos dados.

    revisitando o modelo de roteamento do lado do cliente

    o roteamento do lado do cliente é um processo multi-etapas que começa com a navegação e termina com o roteamento de renderização em si é apenas um passo nesse processo! Vamos rever os detalhes.:

    • navegação – tudo começa com uma mudança de localização. Existem 2 tipos de navegação: interna e externa. A navegação interna é realizada a partir de dentro do aplicativo (eg. através da API histórico), enquanto a navegação externa ocorre quando o usuário interage com a barra de navegação do navegador ou entra na aplicação a partir de um site externo.
    • respondendo à navegação-quando a localização muda, a aplicação responde passando a nova localização para o roteador. Técnicas de roteamento mais antigas confiavam em sondagens window.location para conseguir isso, mas hoje em dia temos a utilidade history.listen útil.
    • Routing-Next, The new location is matched to its corresponding page content. O código que lida com este passo é chamado de roteador, e geralmente leva um parâmetro de entrada de rotas correspondentes e páginas chamadas de configuração de rota. Renderização-finalmente, o conteúdo é renderizado no cliente. Este passo pode, naturalmente, ser tratado por um framework front-end/library como React.

    Note que as bibliotecas de roteamento não têm que lidar com todas as partes do modelo de roteamento.

    Algumas bibliotecas, como Reagir Roteador e Vue Roteador, faça — enquanto outros, como o Universal Roteador, estão preocupados exclusivamente com um único aspecto (como roteamento), proporcionando flexibilidade em outros aspectos:

    Encaminhamento bibliotecas podem ter diferentes âmbitos de responsabilidade. (Clique para ampliar)

    revisitar o modelo de ciclo de vida dos dados Redux

    Redux possui um modelo de fluxo de dados unidirecional/ciclo de vida que provavelmente não precisa de introdução — mas aqui está uma breve visão geral para uma boa medida:

    • Action-Any change in state starts by dispatching a Redux action (a plain object containing a type and optional payload).Middleware-a ação passa pela cadeia de middlewares da loja, onde as ações podem ser interceptadas e o comportamento adicional pode ser executado. Middlewares são comumente usados para lidar com efeitos colaterais em aplicações Redux.
    • redutor — a ação então atinge o redutor raiz, que calcula o próximo estado da loja como uma função pura do estado anterior e a ação recebida. O redutor de raiz pode ser composto de redutores individuais que cada pega uma fatia do estado da loja.
    • Novo Estado — a loja salva o novo estado retornado pelo redutor, e notifica seus assinantes da mudança (em reação, via connect).
    • renderização-finalmente, a vista ligada ao armazém pode re-renderizar de acordo com o novo estado.

    construir um modelo Redux-primeiro roteamento

    a natureza unidirecional do roteamento do lado do cliente e os modelos de ciclo de vida de dados Redux prestam-se bem a um modelo mesclado que satisfaz os critérios que estabelecemos para Redux-primeiro roteamento.

    neste modelo, o roteador é subscrito à loja, a navegação é realizada através de ações de Redux, e as atualizações do histórico do navegador são tratadas por um middleware personalizado. Vamos examinar os detalhes deste modelo:

    • navegação interna através de acções Redux — em vez de usar a API de História directamente, a navegação interna é alcançada através do envio de uma de 5 acções de navegação que espelham os métodos de navegação de história.
    • Updating the browser history via middleware-A middleware is used to intercept the navigation actions and handle the side-effect of updating the browser history. Uma vez que a nova localização não é necessariamente ou facilmente conhecida sem consultar primeiro o histórico do navegador (por exemplo. no caso de uma ação go, as ações de navegação são impedidas de alcançar o redutor.
    • respondendo à navegação-o fluxo de execução continua com um history ouvinte que responde à navegação (tanto do middleware quanto da navegação externa) através do envio de uma segunda ação que contém a nova localização.
    • redutor de localização — a ação despachada pelo ouvinte então chega ao redutor de localização, que adiciona a localização para a loja. O redutor de localização também determina a forma do Estado de localização.
    • roteamento conectado — o roteador conectado à loja pode então determinar de forma reativa o conteúdo da nova página quando notificado de uma mudança de localização na loja.Renderização-finalmente, a página pode ser re-renderizada com o novo conteúdo.

    Note-se que esta não é a única maneira de realizar Redux-primeiro encaminhamento — algumas variações característica a utilização de uma loja enhancer e/ou lógica adicional no middleware, mas é um modelo simples que cobre todas as bases.

    uma implementação básica

    seguindo o modelo que acabamos de ver, vamos implementar a API principal-as ações, middleware, ouvinte e redutor.

    usaremos o pacote history como uma dependência interna, e construiremos a solução gradualmente. Se preferir acompanhar o resultado final, pode vê-lo aqui.

    Actions

    we’ll start by defining the 5 navigation actions that mirror the history navigation methods:

    // constants.jsexport const PUSH = 'ROUTER/PUSH';export const REPLACE = 'ROUTER/REPLACE';export const GO = 'ROUTER/GO';export const GO_BACK = 'ROUTER/GO_BACK';export const GO_FORWARD = 'ROUTER/GO_FORWARD';
    // actions.jsexport const push = (href) => ({ type: PUSH, payload: href,});
    export const replace = (href) => ({ type: REPLACE, payload: href,});
    export const go = (index) => ({ type: GO, payload: index,});
    export const goBack = () => ({ type: GO_BACK,});
    export const goForward = () => ({ type: GO_FORWARD,});

    Middleware

    a seguir, vamos definir o middleware. Ele deve interceptar as ações de navegação, chamar a correspondente history métodos de navegação e, em seguida, parar a ação de alcançar o redutor — mas deixe todas as outras ações imperturbável:

    // middleware.jsexport const routerMiddleware = (history) => () => (next) => (action) => { switch (action.type) { case PUSH: history.push(action.payload); break; case REPLACE: history.replace(action.payload); break; case GO: history.go(action.payload); break; case GO_BACK: history.goBack(); break; case GO_FORWARD: history.goForward(); break; default: return next(action); }};

    Se você ainda não teve a chance de escrever ou examinar os detalhes internos de um Redux middleware antes, confira essa introdução.

    ouvinte de História

    a seguir, vamos precisar de um history ouvinte que responde à navegação através do envio de uma nova ação contendo a nova informação de localização.

    Primeiro, vamos adicionar o novo tipo de ação e criador. As partes interessantes do local são o pathname, search, e hash — então é isso que nós vamos incluir na carga:

    // constants.jsexport const LOCATION_CHANGE = 'ROUTER/LOCATION_CHANGE';
    // actions.jsexport const locationChange = ({ pathname, search, hash }) => ({ type: LOCATION_CHANGE, payload: { pathname, search, hash, },});

    em Seguida, vamos escrever a função de ouvinte:

    // listener.jsexport function startListener(history, store) { history.listen((location) => { store.dispatch(locationChange({ pathname: location.pathname, search: location.search, hash: location.hash, })); });}

    Nós vamos fazer uma pequena adição inicial locationChange despacho, para a conta para a entrada inicial para o aplicativo (que não são apanhados pela história ouvinte):

    // listener.jsexport function startListener(history, store) { store.dispatch(locationChange({ pathname: history.location.pathname, search: history.location.search, hash: history.location.hash, }));
     history.listen((location) => { store.dispatch(locationChange({ pathname: location.pathname, search: location.search, hash: location.hash, })); });}

    Redutor

    em seguida, vamos definir a localização do redutor. Usaremos uma forma de Estado simples, e faremos o mínimo trabalho no redutor.:

    // reducer.jsconst initialState = { pathname: '/', search: '', hash: '',};
    export const routerReducer = (state = initialState, action) => { switch (action.type) { case LOCATION_CHANGE: return { ...state, ...action.payload, }; default: return state; }};

    código de Aplicação

    finalmente, vamos ligar a nossa API ao código de Aplicação:

    // index.jsimport { combineReducers, applyMiddleware, createStore } from 'redux'import { createBrowserHistory } from 'history'import { routerReducer } from './reducer'import { routerMiddleware } from './middleware'import { startListener } from './listener'import { push } from './actions'
    // Create the history objectconst history = createBrowserHistory()
    // Build the root reducerconst rootReducer = combineReducers({ // ...otherReducers, router: routerReducer,}) // Build the middlewareconst middleware = routerMiddleware(history)
    // Create the storeconst store = createStore(rootReducer, {}, applyMiddleware(middleware))
    // Start the history listenerstartListener(history, store)
    // Now you can read location data from the store!let currentLocation = store.getState().router.pathname
    // You can also subscribe to changes in the location!let unsubscribe = store.subscribe(() => { let previousLocation = currentLocation currentLocation = store.getState().router.pathname
     if (previousLocation !== currentLocation) { // You can render your application reactively here! }})
    // And you can dispatch navigation actions from anywhere!store.dispatch(push('/about'))

    e é só isso! Usando a nossa pequena API (abaixo de 100 linhas de código), cumprimos todos os critérios para Redux-primeiro roteamento:

    • a localização é realizada na loja Redux. ✔
    • a localização é alterada através do envio de acções de Redux. ✔
    • a aplicação lê os dados de localização apenas a partir da loja. ✔
    • o histórico da loja e do navegador são mantidos em sincronia nos bastidores. ✔

    veja todos os arquivos juntos aqui-sinta-se livre para importá-los em seu projeto, ou usá-lo como um ponto de partida para desenvolver a sua própria implementação.

    the redux-first-routing package

    i’ve also put the API together into the redux-first-routing package, which you may npm install and use in the same way.

    mksarge/redux-first-routing
    redux-first — routing-a minimal, framework-agnostic base for accomplishing Redux-first routing.github.com

    inclui uma implementação semelhante à que construímos aqui, mas com a adição notável de análise de consulta via o pacote query-string.

    espere-e o componente de encaminhamento?

    Você pode ter notado que redux-first-routing só está preocupada com a navegação aspecto do modelo de roteamento:

    dissociando a navegação aspecto de outros aspectos do nosso encaminhamento modelo, que ganhou alguma flexibilidade — redux-first-routing é tanto router-agnóstico, e quadro-agnóstico.

    pode, portanto, associá-lo a uma biblioteca como o Router Universal para criar uma solução de encaminhamento Redux-first completa para qualquer framework front-end:

    Clique aqui para começar com redux-first-routing + universal-router.

    ou, você pode construir ligações opinionadas para a sua estrutura de escolha — e é isso que faremos para reagir na próxima e última seção deste artigo.

    utilização com React

    vamos terminar a nossa exploração olhando como podemos construir componentes conectados a lojas para a navegação declarativa e roteamento em Reat.

    Declarative Navigation

    For navigation, we can use a store-connected <Link / > component similar to the one in React Router and other React routing solutions.

    sobrepõe-se simplesmente ao comportamento por omissão do elemento âncora <a / > e ao dispatches uma acção por pressão quando clicado:

    // Link.jsimport React from 'react';import { connect } from 'react-redux';import { push as pushAction, replace as replaceAction } from './actions';
    const Link = (props) => { const { to, replace, children, dispatch, ...other } = props;
     const handleClick = (event) => { // Ignore any click other than a left click if ((event.button && event.button !== 0) || event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || event.defaultPrevented === true) { return; } // Prevent the default behaviour (page reload, etc.) event.preventDefault();
     // Dispatch the appropriate navigation action if (replace) { dispatch(replaceAction(to)); } else { dispatch(pushAction(to)); } };
     return ( <a href={to} onClick={handleClick} {...other}> {children} </a>);};
    export default connect()(Link);

    Você pode encontrar uma implementação mais completa aqui.

    roteamento declarativo

    embora não haja muito para um componente de navegação, existem inúmeras maneiras de projetar um componente de roteamento — tornando-o a parte mais interessante de qualquer solução de roteamento.

    o que é um router, afinal?

    pode geralmente ver um router como uma função ou caixa negra com duas entradas e uma saída:

    route configuration ↘ matched content current location ↗

    embora o Encaminhamento e posterior renderização possam ocorrer em etapas separadas, React torna fácil e intuitivo juntá-los em uma API declarativa de encaminhamento. Vamos olhar para duas estratégias para realizar isso.

    Estratégia 1: monolítico <Router/> componente

    podemos usar um monolítico, o armazenamento ligado <Router/> componente que:

    • aceita um objeto de configuração de rota via props
    • lê os dados de localização Da Loja Redux
    • calcula o novo conteúdo sempre que a localização muda
    • rende/rende o conteúdo conforme apropriado.

    a configuração de rota pode ser um objeto javascript simples que contém todos os caminhos e páginas correspondentes (uma configuração de rota centralizada).Aqui está como isto pode parecer:

    const routes = 
    React.render( <Provider store={store}> <Router routes={routes}> </Provider>, document.getElementById('app'))

    muito simples, não é? Não há necessidade de rotas JSX aninhadas — apenas um único objeto de configuração de rota, e um único componente de roteador.

    se esta estratégia é atraente para você, confira minha implementação mais completa na biblioteca redux-json-router. Ele envolve redux-first-routing e fornece atalhos de reação para a navegação declarativa e roteamento usando as estratégias que examinamos até agora.

    mksarge/redux-json-router
    redux-json-router-Declarative, Redux-first routing for React/Redux browser applications.github.com

    estratégia 2: Composable <Route / > components

    While a monolithic component may be a simple way to achieve declarative routing in React, it’s definitely not the only way.

    a natureza composível da reacção permite outra possibilidade interessante: utilizar o JSX para definir as rotas de uma forma descentralizada. Claro, o exemplo principal é Reat Router ‘ s <Route / > API:

    React.render( <BrowserRouter> <Route path='/' component={Home}/> <Route path='/about component={About}/> ... </BrowserRouter>

    outras bibliotecas de roteamento exploram esta idéia também. Embora eu não tenha tido a chance de fazê-lo, eu não vejo nenhuma razão pela qual uma API semelhante não poderia ser implementada em cima do pacote redux-first-routing.

    em Vez de depender de dados de localização fornecidos por <BrowserRouter/>, the &lt;Rota/> componente could simply ligar para a loja:

    React.render( <Provider store={store}> <Route path='/' component={Home}/> <Route path='/about component={About}/> ... </Provider>

    Se isso é algo que você está interessado em construir ou usando, deixe-me saber nos comentários! Para saber mais sobre diferentes estratégias de configuração de rotas, confira esta introdução no site do router Reat.

    conclusão

    espero que esta exploração tenha ajudado a aprofundar o seu conhecimento sobre o encaminhamento do lado do cliente e lhe mostrou o quão simples é realizá-lo da maneira Redux.

    se está à procura de uma solução completa de roteamento de Redux, pode usar o pacote redux-first-routing com um roteador compatível listado no readme. E se você encontrar a necessidade de desenvolver uma solução personalizada, espero que este post lhe deu um bom ponto de partida para fazê-lo.

    se você gostaria de saber mais sobre roteamento do lado do cliente em Redux E Redux, confira os seguintes artigos-eles foram fundamentais para me ajudar a entender melhor os tópicos que eu abordei aqui:Você pode não precisar de um Router de reação de Konstantin Tarkus. por James K. Nelson

  • e inúmeras discussões informativas nas edições de react-router-redux.

o encaminhamento do lado do cliente é um espaço com infinitas possibilidades de design, e tenho a certeza de que alguns de vós já brincaram com ideias semelhantes às que partilhei aqui. Se você quiser continuar a conversa, terei o prazer de me conectar com você nos comentários ou via Twitter. Obrigado por ler!

Edit 22/06/17: Confira também este artigo em redux-first-router, um projeto separado que usa tipos de ação inteligentes para alcançar poderosas capacidades de roteamento.

Deixe uma resposta

O seu endereço de email não será publicado.