在 react-router-dom 中使用 BrowserRouter 的动态基名

dynamic basename with BrowserRouter in react-router-dom

我在构建多租户 SaaS 解决方案时遇到了问题。对于每个租户,我希望他们使用一个子域,这样我就可以从 url 获取子域,调用 REST api 获取有关该租户的 returns 数据。

例如,

  1. 管理员(完全是另一个应用程序 - 管理应用程序)创建了一个域名为:tenant1 的租户。
  2. 在我本地系统上的租户应用程序中,我能够转到 tenant1.localhost:3000。我得到 url,并得到域名。然后我用域调用以获取租户的主题(这存储在 localStorage 中)。

不幸的是,我们在我公司的 k8 上部署,所以我无法模仿这种行为。因此,devOps 团队建议我在上下文中使用子域,从而得到 localhost:3000/tenant1。请记住租户是动态的,所以我尝试了这个:

<BrowserRouter basename={"/:tenant"}>
    <Switch>
        <Route exact path="/login" name="Login" component={Login} />
        <Route exact path="/set-password/:token" name="Set Password" component={SetPassword} />
        <PrivateRoute path="/" name="Default Layout" component={DefaultLayout} /> 
    </Switch>              
</BrowserRouter>

上面的解决方案使我的 url 变为 localhost:3000/:tenant/login

请问我如何在路由器中使用动态基名,以便它可以接受:

localhost:3000/tenant1 localhost:3000/tenant3 localhost:3000/tenant2

它可以允许任何,我的应用程序处理输入的错误域

我终于用下面的代码使用了动态租户

class App extends Component {

    state = {
        domain: ""
    }

    componentWillMount () {
        const { domain } = this.state;

        const parsedData = window.location.pathname.split("/"); 
        let domain = parsedData[1];
        this.setState({ domain: domain })
        this.props.onGetTenant(domain);                
    }

    render () {
        const { domain } = this.state;

        return () {
             <BrowserRouter basename={"/"+domain}>
                <Switch>
                    <Route exact path="/login" name="Login" component={Login} />
                    <Route exact path="/set-password/:token" name="Set Password" component={SetPassword} />
                    <PrivateRoute domain={domain} path="/" name="Default Layout" component={DefaultLayout} /> 
                 </Switch>              
             </BrowserRouter> 
    }

const mapStateToProps = state => {
    const { tenant} = state;
    return { tenant};
};

const mapDispatchToProps = (dispatch) => {
    return {
        onGetTenant: bindActionCreators( tenantActions.get, dispatch)
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(App)

这对我有用 react >16 和 react-router-dom v5

export const App = () => {
    return (
        <BrowserRouter>
            <Switch>
                <Route path="/:tenantId?" component={LayoutRoot} />
            </Switch>
        </BrowserRouter>
    );
};

export const LayoutRoot = () => {
    var { tenantId } = useParams();
     //TODO: add some validation here and inform user if tenant is invalid
    return (
        <BrowserRouter basename={tenantId}>
            <Switch>
                <Route path="/login" component={LoginComponent} />
                <Route path="/dashboard" component={DashboardComponent} />
            </Switch>
        </BrowserRouter>
    );
};

这是一个codesandbox和我写的实用程序:

https://codesandbox.io/s/react-router-dom-dynamic-basename-xq9tj?file=/index.js

import urlJoin from 'url-join';

// it's important to have an identifier in their app
export const APP_ROOT_URL = '/my-app';

export const getBaseUrlPath = () => {
  const currentPath = document.location.pathname || APP_ROOT_URL;
  const startOfAppBase = currentPath.indexOf(APP_ROOT_URL);

  let base = currentPath;

  if (startOfAppBase !== -1) {
    base = currentPath.substr(0, startOfAppBase);
  }

  base = urlJoin(base, APP_ROOT_URL);

  return base;
};

您可以使用 key 属性 呈现路由器 basename 的更新。对键值的任何更改都会导致组件重新呈现。

这里有一个代码沙箱来演示: https://codesandbox.io/s/react-router-dom-dynamic-basename-forked-hnkk0?file=/index.js

您可以悬停或检查沙盒中的链接,以验证它们的 href 值在更改基本名称后是否正确更新。如果从 Router.[=18 中删除 key 属性,您还可以看到 hrefs 不会 更新=]

import React, { useState } from "react";
import { render } from "react-dom";
import { BrowserRouter, Link } from "react-router-dom";

const Root = () => {
  const [count, setCount] = useState(1);
  const basename = `basename-${count}`;

  return (
    <BrowserRouter basename={basename} key={basename}>
      <Link to="/link1">Link 1</Link>
      <br />
      <Link to="/link2">Link 2</Link>
      <br />
      Current basename: {basename}
      <br />
      <button onClick={() => setCount(count + 1)}>change basename</button>
    </BrowserRouter>
  );
};

render(<Root />, document.getElementById("root"));