13

I have non-SPA server-side application with React application that is limited to current page, /some/static/page. The application has <base href="/"> in <head> on all pages and relies on it, this cannot be changed.

Here is basic example with React 16, React Router 4 and <HashRouter>:

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

All routes can be disabled for testing purposes, but this doesn't change the behaviour.

Here is create-react-app project that shows the problem. The steps to replicate it are:

  • npm i
  • npm start
  • navigate to http://localhost:3000/some/static/page

HashRouter is clearly affected by <base>. It redirects from /some/static/page to /#/ on initialization, while I expect it to be /some/static/page#/ or /some/static/page/#/ (works as intended only in IE 11).

There's a quick splash of Root component before it redirects to /#/.

It redirects to /foo/#/ in case of <base href="/foo">, and it redirects to /some/static/page/#/ when <base> tag is removed.

The problem affects Chrome and Firefox (recent versions) but not Internet Explorer (IE 11).

Why is <HashRouter> affected by <base>? It's used here exactly because it isn't supposed to affect location path, only hash.

How can this be fixed?

4

5 回答 5

11

其实这来自history. 如果您看到他们的代码,他们会使用 justcreateHashHistory和 set children。所以它相当于:

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

它将显示您遇到的相同问题。然后,如果您更改history这样的代码:

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

那么你的问题就会消失,但绝对不会使用hash. 所以问题不是来自, HashRouter而是来自history

因为这个来自history,让我们看看这个线程。阅读该线程后,我们可以得出结论,这是来自history.

因此,如果您设置<base href="/">,因为您使用的是hash(#),当浏览器加载时(实际上是在 之后componentDidMount),它将hash在您的情况下附加 (#) some/static/page=> some/static/page+ /=> /+ #/=> /#/。您可以在附加路线之前签入componentDidMount集合debugger以捕获。


解决方案

简单地说,只需删除元素<base href>或不使用HashRouter.

如果仍然需要但想避免具体component的,只需将其放在前面class

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

更新

因为您想保留base标签以保持持久链接并使用hash路由器,所以我认为这是关闭的解决方案。

1. 将标签设置base为空。

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

将该代码放入App组件(根包装组件)中以调用一次。

2.componentDidMount放回时

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

使用超时等待反应完成渲染虚拟 dom。

我认为这非常接近(已经测试过)。因为您使用的是hash路由器,所以来自 index html 的链接将是安全的(不是通过反应覆盖,而是通过base标记保留)。它也适用于 css 链接<link rel="stylesheet" href="styles.css">

于 2018-03-28T16:59:15.610 回答
2

我以 HOC 结束,它只是应用了这个答案中描述的修复:

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>
...
于 2018-04-01T15:00:21.180 回答
2

你的观察HashRouter<base>标签是正确的。我在此处提交了有关浏览器差异的问题:https ://github.com/ReactTraining/history/issues/574和相应的 PR 并在此处进行了修复:https ://github.com/ReactTraining/history/pull/577

同时,我不确定您需要的所有路由,但如果 react 应用程序完全位于 下/some/static/page/,您可能可以使其与:

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

于 2018-03-29T08:08:14.570 回答
1

这是history包装的问题。已经解决了,请看这个pr

作为临时修复,我建议您只指定此分支package.json

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

一旦修复将被合并到原始分支中 - 将其恢复为固定的主 npm 模块


关于 repo:我刚刚npm run buildmicrobouji 解决方案publish上做了并提交了结果,因为不运行脚本就无法使用原始存储库

于 2018-04-02T19:42:52.620 回答
1

如果您看到https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#Hint,它表示<base>即使使用 #target URLs 也是预期行为。

https://reacttraining.com/react-router/web/api/HashRouter上,它在 basename: string 部分中说:格式正确的 basename 应该有一个前导斜杠,但没有尾随斜杠

因此,也许您应该在HashRouter元素上定义不同的基本名称或从<base>

于 2018-03-28T12:48:00.360 回答