web-dev-qa-db-fra.com

React Le routeur HashRouter redirige vers l'URL de la balise <base>

J'ai une application côté serveur non-SPA avec React application limitée à la page actuelle, /some/static/page. L'application a <base href="/"> dans <head> sur toutes les pages et en dépend, cela ne peut pas être changé.

Voici un exemple de base avec React 16, React Router 4 and <HashRouter>:

export class App extends React.Component {
    render() {
        return (
            <HashRouter>
                <div>
                    <Route exact path="/" component={Root} />
                </div>
            </HashRouter>
        );
    }
}

Tous les itinéraires peuvent être désactivés à des fins de test, mais cela ne change pas le comportement.

Voici create-react-app project qui montre le problème. Les étapes pour le répliquer sont:

  • npm i
  • npm start
  • aller vers http://localhost:3000/some/static/page

HashRouter est clairement affecté par <base>. Il redirige depuis /some/static/page à /#/ à l'initialisation, alors que je m'attends à ce que ce soit /some/static/page#/ ou /some/static/page/#/ (fonctionne comme prévu uniquement dans IE 11).

Il y a une éclaboussure rapide du composant Root avant qu'il ne redirige vers /#/.

Il redirige vers /foo/#/ en cas de <base href="/foo">, et il redirige vers /some/static/page/#/ quand <base> la balise est supprimée.

Le problème affecte Chrome et Firefox (versions récentes) mais pas Internet Explorer (IE 11).

Pourquoi est-ce <HashRouter> affecté par <base>? Il est utilisé ici exactement parce qu'il n'est pas censé affecter le chemin de localisation, mais uniquement le hachage.

Comment résoudre ce problème?

10
Estus Flask

En fait, cela à partir de history. Si vous voyez leur code , ils utilisent simplement createHashHistory et définissent children. Donc, cela équivaut à ceci:

import React from 'react';
import { Route, Router } from 'react-router-dom';
import { createHashHistory } from 'history';

const Root = () => <div>Root route</div>;
export default class App extends React.Component {

  history = createHashHistory({
    basename: "", // The base URL of the app (see below)
    hashType: "slash", // The hash type to use (see below)
    // A function to use to confirm navigation with the user (see below)
    getUserConfirmation: (message, callback) => callback(window.confirm(message)),
  });


  render() {
    return (

      <Router history={this.history}>
      <div>Router
        <Route exact path="/" component={Root} />
      </div>
      </Router>
      );
    }
}

Il montrera le même problème que vous. Ensuite, si vous changez le code history comme ceci:

import {createBrowserHistory } from 'history';

...

history = createBrowserHistory({
    basename: "", // The base URL of the app (see below)
    forceRefresh: false, // Set true to force full page refreshes
    keyLength: 6, // The length of location.key
    // A function to use to confirm navigation with the user (see below)
    getUserConfirmation: (message, callback) => callback(window.confirm(message))
});

alors votre problème disparaîtra mais n'utilisez certainement pas hash. Le problème ne vient donc pas de HashRouter mais de history.

Parce que cela vient de history, voyons cela thread . Après avoir lu ce fil, nous pouvons conclure que c'est la fonctionnalité de history.

donc, si vous définissez <base href="/">, car vous utilisez hash (#), lorsque le navigateur est chargé (en fait après componentDidMount), il ajoutera hash (#) dans votre cas some/static/page => some/static/page + / => / + #/ => /#/. Vous pouvez archiver componentDidMount définir debugger pour intercepter avant d'ajouter l'itinéraire.


SOLUTION

simplement, supprimez simplement l'élément <base href> ou n'utilisez pas HashRouter.

Si vous avez toujours besoin, mais que vous souhaitez éviter, de component spécifique, mettez-le juste avant class:

const base = document.querySelector("base");
base.setAttribute('href', '');

MISE À JOUR

puisque vous voulez garder la balise base pour garder le lien persistant et utiliser le routeur hash, voici la solution proche, je pense.

1. Définissez la balise base sur vide.

const base = document.querySelector('base');
base.setAttribute('href', '');

placez ce code dans le composant App (composant d'enveloppement racine) pour appeler une fois.

2. Lorsque componentDidMount le rétablit

componentDidMount() {
  setTimeout(() => {
    base.setAttribute('href', '/');
  }, 1000);
}

en utilisant le délai d'attente pour attendre réagir fait rendre le dom virtuel.

C'est très proche, je pense (à tester). Parce que vous utilisez le routeur hash, le lien à partir de l'index html sera sûr (ne pas remplacer par react mais conserver par base tag). Cela fonctionne aussi avec le lien css <link rel="stylesheet" href="styles.css"> ainsi que.

2
hendrathings

Si vous voyez https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#Hint , cela indique que l'utilisation de <base> même avec les URL #target est un comportement attendu.

Et sur https://reacttraining.com/react-router/web/api/HashRouter il dit dans basename: section de chaîne: Un nom de base correctement formaté devrait avoir une barre oblique, mais pas de barre oblique .

Donc, vous devriez peut-être définir un nom de base différent sur l'élément HashRouter ou supprimer la barre oblique de fin de <base>

1
Dhruv Murarka

C'est un problème de history paquet. Il est même résolu, veuillez regarder ce pr

Comme correctif temporaire, je vous suggère de simplement spécifier cette branche dans package.json

"dependencies": {
  ...
  "history": "git://github.com/amuzalevskiy/history.git",
  ...
}

Et une fois que le correctif sera fusionné dans la branche d'origine - revenez au module npm principal fixe


Concernant le repo: je viens de faire npm run build sur solution microbouji et résultat validé, car il est impossible d'utiliser le dépôt d'origine sans exécuter le script publish

1

J'ai terminé avec HOC qui applique simplement un correctif décrit dans cette réponse :

function withBaseFix(HashRouter) {
    return class extends React.Component {
        constructor() {
            super();
            this.baseElement = document.querySelector('base');
            if (this.baseElement) {
                this.baseHref = this.baseElement.getAttribute('href');
                this.baseElement.setAttribute('href', '');
            }
        }

        render() {
            return <HashRouter {...this.props}>{this.props.children}</HashRouter>;
        }

        componentDidMount() {
            if (this.baseElement)
                this.baseElement.setAttribute('href', this.baseHref);
        }
    }
};

const FixedHashRouter = withBaseFix(HashRouter);

...
<FixedHashRouter>
    <div>
        <Route exact path="/" component={Root} />
    </div>
</FixedHashRouter>
...
1
Estus Flask

Votre observation sur HashRouter et le <base> la balise est correcte. J'ai déposé un problème concernant les différences de navigateurs ici: https://github.com/ReactTraining/history/issues/574 et le PR correspondant avec correction ici: https: // github. com/ReactTraining/histoire/pull/577

En attendant, je ne suis pas sûr de tout le routage dont vous avez besoin, mais si l'application react vit entièrement sous /some/static/page/, vous pouvez probablement le faire fonctionner avec:

<BrowserRouter basename="/some/static/page">.

1
Elian Ibaj