Jak zatrzymać / # / w przeglądarce z React-routerem?

103

Czy jest jakiś sposób, aby zapobiec /#/wyświetlaniu się w pasku adresu przeglądarki podczas korzystania z React-router? Tak jest z ReactJS. tzn. kliknięcie linków, aby przejść do nowej trasy pokazuje localhost:3000/#/lub localhost:3000/#/about. W zależności od trasy.

Gigantyczny łoś
źródło
1
Wynika to z użycia HashHistoryiso BrowserHistory. Zobacz także to pytanie SO, w którym podaję wiele ogólnych informacji na ten temat.
Stijn de Witt,

Odpowiedzi:

78

W przypadku wersji 1, 2 i 3 React-router prawidłowym sposobem ustawiania trasy do schematu mapowania adresów URL jest przekazanie implementacji historii do historyparametru <Router>. Z dokumentacji historycznej :

Krótko mówiąc, historia wie, jak nasłuchiwać paska adresu przeglądarki pod kątem zmian i analizuje adres URL w obiekt lokalizacji, którego router może użyć do dopasowania tras i renderowania prawidłowego zestawu komponentów.

Wersje 2 i 3

W routerach reakcji 2 i 3 kod konfiguracji trasy będzie wyglądał mniej więcej tak:

import { browserHistory } from 'react-router'
ReactDOM.render (( 
 <Router history={browserHistory} >
   ...
 </Router> 
), document.body);

Wersja 1

W wersji 1.x zamiast tego użyjesz:

import createBrowserHistory from 'history/lib/createBrowserHistory'
ReactDOM.render (( 
  <Router history={createBrowserHistory()} >
   ...
  </Router> 
), document.body);

Źródło: przewodnik aktualizacji do wersji 2.0

Wersja 4

W nadchodzącej wersji 4 reakcji-routera składnia uległa znacznej zmianie i wymagane jest użycie BrowserRouterjako znacznika głównego routera.

import BrowserRouter from 'react-router/BrowserRouter'
ReactDOM.render (( 
  <BrowserRouter>
   ...
 <BrowserRouter> 
), document.body);

Source React Router wersja 4 Docs

Adam Brown
źródło
6
Pamiętaj, że historyjest to samodzielny pakiet, który musisz zainstalować.
Jan Klimo
4
Zmienili browserHistoryw v2.x: import { browserHistory } from 'react-router' <Router history={browserHistory} />Sprawdź przewodnik aktualizacji
reaktora
Dzięki @pistou, zaktualizowałem odpowiedź do wersji 2.0!
Adam Brown,
1
Bo hashHistoryczy istnieje sposób na pozbycie się tego parametru zapytania na końcu? http://localhost:8080/#/dashboard?_k=yqwtyu
Con Antonakos
2
@Matt To działa, ale wymaga wsparcia na serwerze. Dzieje się tak, ponieważ podczas odświeżania trafiasz na serwer z adresem URL ze ścieżką.
Stijn de Witt,
40
Router.run(routes, Router.HistoryLocation, function (Handler) {
  React.render(<Handler/>, document.body);
});

W przypadku aktualnej wersji 0.11 i nowszych musisz dodać Router.HistoryLocationdo Router.run(). <Routes>są teraz przestarzałe. Zapoznaj się z podręcznikiem aktualizacji, aby zapoznać się z implementacją wersji 0.12.x HistoryLocation.

pxwise
źródło
1
to całkowicie zrujnowało moją aplikację. wygląda na to, że ich obecna implementacja zawiera błędy?
ninjaneer
2
@Ninja może opublikować nowe pytanie z dokładnymi numerami wersji dla routera reagowania i reagowania, błędnego kodu i otrzymanych błędów.
pxwise
@Chet Wygląda na to, że dokumenty reaktora routera zostały przetasowane. Zaktualizowano łącze do jedynego odniesienia do HistoryLocation znajdującego się w Przewodniku aktualizacji.
pxwise
21

Jeśli nie potrzebujesz obsługi IE8, możesz użyć historii przeglądarki, a reakcja routera użyje window.pushState zamiast ustawiania skrótu.

Jak to zrobić, zależy od używanej wersji React Router:

Sophie Alpert
źródło
Dzięki @ ben-alpert, teraz rozumiem.
Giant Elk
1
Dodałem, <Routes location="history">że wszystko działa OK, dopóki nie odświeżysz przeglądarki na trasie czyli localhost:3000/aboutwtedy wyskakuje mi błąd 404. Czy to jest oczekiwane, używam python -m SimpleHTTPServer 3000?
Giant Elk
5
Musisz się upewnić, że strona serwera może obsłużyć adres URL stanu wypychania. W tym przypadku prawdopodobnie oznacza to, że musisz się upewnić, że wszystko, co obsługuje twoją aplikację, zawsze wysyła każdy adres URL, który dostaje do tego samego katalogu głównego. Więc to /aboutfaktycznie ładuje twoją stronę główną /. W przeciwnym razie serwer próbuje szukać pasującej trasy /abouti nic nie znajduje (404). Osobiście nie używam Pythona, ale zwykle znajdujesz ręczną trasę dla /*lub /.*-> /działa - lub może to być coś, co nazywa się html5Modeadresami URL w ustawieniach serwera.
Mike Driver
3
IE9 również nie obsługuje pushState - więc tak naprawdę jest "Jeśli nie potrzebujesz obsługi IE9", prawda? Chciałabym się mylić.
Cymen
1
Ten link do github to strona, której nie znaleziono teraz.
k00k
9

Aby to osiągnąć, możesz użyć .htaccess. Przeglądarka zwykle potrzebuje ogranicznika ciągu zapytania ?lub #określenia, gdzie zaczyna się ciąg zapytania i kończą się ścieżki katalogu. Wynik końcowy, jakiego oczekujemy, jest www.mysite.com/dir taki, więc musimy wykryć problem, zanim serwer sieciowy wyszuka katalog, o który według niego prosiliśmy /dir. Więc umieszczamy .htaccessplik w katalogu głównym projektu.

    # Setting up apache options
    AddDefaultCharset utf-8
    Options +FollowSymlinks -MultiViews -Indexes
    RewriteEngine on

    # Setting up apache options (Godaddy specific)
    #DirectoryIndex index.php
    #RewriteBase /


    # Defining the rewrite rules
    RewriteCond %{SCRIPT_FILENAME} !-d
    RewriteCond %{SCRIPT_FILENAME} !-f

    RewriteRule ^.*$ ./index.html

Następnie uzyskujesz parametry zapytania z window.location.pathname

Możesz wtedy uniknąć korzystania z tras reakcji, jeśli chcesz, i po prostu manipulować adresem URL i historią przeglądarki, jeśli chcesz. Mam nadzieję, że to komuś pomoże ...

Garrett Tacoronte
źródło
Jaki jest odpowiednik Jbossa?
Raghavan
5

Zainstaluj pakiet historii

npm install history --save

Następnie zaimportuj z historii createHistory i useBasename

import { createHistory, useBasename } from 'history';
...
const history = useBasename(createHistory)({
  basename: '/root' 
});

jeśli adres URL Twojej aplikacji to www.example.com/myApp, wówczas / root powinno brzmieć / myApp.

przekazać zmienną historii do routera

render((
  <Router history={history}>
    ...
  </Router>
), document.getElementById('example'));

Teraz dla wszystkich tagów Link dodaj „/” przed wszystkimi ścieżkami.

<Link to="/somewhere">somewhere</Link>

Inspiracją dla rozwiązania był przykład React-Router, który niestety nie został odpowiednio udokumentowany w ich API.

Mox
źródło
czy to wymaga serwera węzłowego? Próbuję osiągnąć ten sam styl adresu URL, ale tylko po stronie klienta. Czy to możliwe?
Sebastialonso,
1
nie, nie potrzebujesz serwera węzłowego. W rzeczywistości pracuję na zapleczu django. Ale prawdopodobnie potrzebujesz węzła do narzędzi.
Mox
1
Ok, próbowałem tego podejścia. Kiedy naciskam F5, wszystko co otrzymuję to „Nie znaleziono”. Czy ta historia może sobie z tym poradzić?
Sebastialonso
jeśli nie zostaniesz znaleziony, to jest zwracane przez serwer. Oznacza to, że wzorzec adresu URL nie jest częścią routera reagującego.
Mox
1
Tak, po dokładniejszym przeczytaniu wszystko stało się jasne. Skończyłem z hashHistory bez kluczy.
Sebastialonso
3

Innym sposobem obsługi tego, co ma być wyświetlane po hashu (więc jeśli nie używasz pushState!), Jest utworzenie CustomLocation i załadowanie go podczas tworzenia ReactRouter.

Na przykład, jeśli chcesz, aby adres URL z hashbangiem (a więc z #!) Był zgodny ze specyfikacjami Google dotyczącymi indeksowania, możesz utworzyć plik HashbangLocation.js, który kopiuje głównie oryginalny HashLocation, taki jak:

'use strict';

var LocationActions = require('../../node_modules/react-router/lib/actions/LocationActions');
var History = require('../../node_modules/react-router/lib/History');

var _listeners = [];
var _isListening = false;
var _actionType;

function notifyChange(type) {
  if (type === LocationActions.PUSH) History.length += 1;

  var change = {
    path: HashbangLocation.getCurrentPath(),
    type: type
  };

  _listeners.forEach(function (listener) {
    listener.call(HashbangLocation, change);
  });
}

function slashToHashbang(path) {
  return "!" + path.replace(/^\//, '');
}

function ensureSlash() {

  var path = HashbangLocation.getCurrentPath();
  if (path.charAt(0) === '/') {
    return true;
  }HashbangLocation.replace('/' + path);

  return false;
}

function onHashChange() {
  if (ensureSlash()) {
    // If we don't have an _actionType then all we know is the hash
    // changed. It was probably caused by the user clicking the Back
    // button, but may have also been the Forward button or manual
    // manipulation. So just guess 'pop'.
    var curActionType = _actionType;
    _actionType = null;
    notifyChange(curActionType || LocationActions.POP);
  }
}

/**
 * A Location that uses `window.location.hash`.
 */
var HashbangLocation = {

  addChangeListener: function addChangeListener(listener) {
    _listeners.push(listener);

    // Do this BEFORE listening for hashchange.
    ensureSlash();

    if (!_isListening) {
      if (window.addEventListener) {
        window.addEventListener('hashchange', onHashChange, false);
      } else {
        window.attachEvent('onhashchange', onHashChange);
      }

      _isListening = true;
    }
  },

  removeChangeListener: function removeChangeListener(listener) {
    _listeners = _listeners.filter(function (l) {
      return l !== listener;
    });

    if (_listeners.length === 0) {
      if (window.removeEventListener) {
        window.removeEventListener('hashchange', onHashChange, false);
      } else {
        window.removeEvent('onhashchange', onHashChange);
      }

      _isListening = false;
    }
  },

  push: function push(path) {
    _actionType = LocationActions.PUSH;
    window.location.hash = slashToHashbang(path);
  },

  replace: function replace(path) {
    _actionType = LocationActions.REPLACE;
    window.location.replace(window.location.pathname + window.location.search + '#' + slashToHashbang(path));
  },

  pop: function pop() {
    _actionType = LocationActions.POP;
    History.back();
  },

  getCurrentPath: function getCurrentPath() {
    return decodeURI(
    // We can't use window.location.hash here because it's not
    // consistent across browsers - Firefox will pre-decode it!
    "/" + (window.location.href.split('#!')[1] || ''));
  },

  toString: function toString() {
    return '<HashbangLocation>';
  }

};

module.exports = HashbangLocation;

Zwróć uwagę na funkcję slashToHashbang .

Po prostu musisz to zrobić

ReactRouter.create({location: HashbangLocation})

I to wszystko :-)

Jonathan Banon
źródło