React Router v5.0 嵌套路由

9
我正在构建一个React应用程序,但无法使路由功能正常工作。
  1. 我需要为一些Auth路由(/loginsign-upforgot-password等)提供一个通用布局(标题,页脚)。

  2. 而我需要为应用程序的其余受保护部分(HomeDashboard等)提供另一个通用布局。

  3. 我需要一个没有任何布局的404页面。

我尝试了以下链接中的多种技术: 但都无法达到可用版本。
这是我目前拥有的内容:
(注意:暂时忽略将非登录用户阻止进入AppLayout的私有路由的需求,我会在此之后处理。)
const App: React.FC = () => {
    const history = createBrowserHistory();

    return (
        <div className="App">
            <Router history={history}>
                <Switch>
                    <AppLayout>
                        <Route path="/home" component={HomePage}/>
                        <Route path="/dashboard" component={DashboardPage}/>
                        ...
                    </AppLayout>
                    <AuthLayout>
                        <Route path="/login" component={LoginPage}/>
                        <Route path="/sign-up" component={SignUpPage}/>
                        ...
                    </AuthLayout>
                    <Route path="*" component={NotFoundPage} />
                </Switch>
            </Router>
        </div>
    );
};

export default App;

AuthLayoutAppLayout都很简单,它们非常相似(只是每个布局的页眉/页脚不同):

class AppLayout extends Component {
    render() {
        return (
            <div className="AppLayout">
                <header>...</header>
                {this.props.children}
                <footer>...</footer>
            </div>
        );
    }
}

export default AppLayout;

问题在于只有来自AppLayout的路由会被呈现。其他路由只是显示了AppLayout的header和footer而没有任何内容。
这些是我使用的React版本:
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-router-dom": "^5.0.0",

任何帮助都将不胜感激。
提前感谢您。
4个回答

20

每个布局都应该有一个路径组件以区别于其他布局。

例如:

认证布局可以位于/auth下,例如登录为/auth/login,注册为/auth/signup

应用程序布局可以位于/app下,例如仪表板为/app/dashboard,主页为/app/home

工作演示

Edit hungry-dubinsky-q1l62

App.js

import { Switch, BrowserRouter, Route, Redirect } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Layouts />
    </BrowserRouter>
  );
}

布局.js

const NotFound = () => <h1>Not Found</h1>;

function Layouts() {
  return (
    <Switch>
      <Route path="/auth" component={AuthLayout} />
      <Route path="/app" component={AppLayout} />
      <Route path="/" component={NotFound} />
    </Switch>
  );
}

认证布局

const Signup = () => <p>Login</p>;
const Login = () => <p>Sign up</p>;

function AuthLayout() {
  return (
    <div>
      <h1>Auth Layout</h1>
      <Route path="/auth/signup" exact component={Signup} />
      <Route path="/auth/login" exact component={Login} />
      <Redirect from="/auth" to="/auth/login" exact />
    </div>
  );
}

应用程序布局

const Home = () => <p>Home</p>;
const Dashboard = () => <p>Dashboard</p>;

function AppLayout() {
  return (
    <div>
      <h1>App Layout</h1>
      <Route path="/app/home" exact component={Home} />
      <Route path="/app/dashboard" exact component={Dashboard} />
      <Redirect from="/app" to="/app/home" exact />
    </div>
  );
}

如果您希望保护某些路由不被呈现,除非已经验证身份,则可以创建一个PrivateRoute组件,如果未通过身份验证,则会重定向到auth布局。

PrivateRoute.js

const PrivateRoute = ({ component: Component, ...rest }) => (
  <Route
    {...rest}
    render={props => sessionStorage.token // your auth mechanism goes here
      ? <Component {...props} />
      : <Redirect to={{ pathname: '/auth' }} />}
  />
);
您可以使用此PrivateRoute组件,代替react-routerRoute组件。

例如:

<PrivateRoute path="/app" component={AppLayout} />


1
谢谢!虽然我更喜欢一个不限制我更改URL(/app,/auth)的解决方案。 - ET-CS
2
这个解决方案帮助我找到了答案。在我看来,它比被接受的答案更好。 - therebelcoder
1
作为一个有 Angular 背景的人,我认为这是 React 中最明智、最干净的路由方法。React 的路由方法太过混乱了,而这个方法则非常清晰易懂,容易扩展。感谢您的分享。 - KhoPhi
1
如果您访问类似于/app/somethingnotfound的地址,它不会跳转到 NotFound 组件。 - Vencovsky
通过从根布局路由中删除“exact”,您应该能够导航到/app/somthing。 - rake7h

7

我花了一些时间找到了我喜欢的答案。 @johnny-peter和@gaurab-kc的解决方案都很棒,它们让我了解了React的路由机制。

@johnny-peter的解决方案的缺点是强制我为所有与auth相关的路由添加前缀/auth/...(例如/auth/loginauth/sign-up),这不是我想要的。

@gaurab-kc的解决方案只支持一个路由集..所以如果用户已经注册,他将无法再访问/login路由。

直到最近我使用了自己的解决方案,但正如@johnny-peter提到的那样,它有“击败共同头和页脚整体目的”的问题,并且被投票反对了几次,这也是应该的。

现在我正在使用另一种解决方案:

<Router history={browserHistory}>
    <Switch>
        <Redirect exact from="/" to="/home"/>
        <Route exact path={["/login", "/sign-up", ...]}>
            <AuthLayout>
                <Switch>
                    <Route
                        path="/login"
                        component={LoginPage}
                    />
                    <Route
                        path="/sign-up"
                        component={SignUpPage}
                    />
                </Switch>
            </AuthLayout>
        </Route>
        <Route exact path={[
            "/home",
            "/dashboard",
            ...
        ]}>
            <SiteLayout>
                <Switch>
                    <Route
                        path="/home"
                        component={HomePage}
                    />
                    <Route
                        path="/dashboard"
                        component={DashboardPage}
                    />
                </Switch>
            </SiteLayout>
        </Route>
        <Route path="*" component={NotFoundPage}/>
    </Switch>
</Router>

这个方案解决了以上所有的缺点。它允许我:
  1. 为每个部分使用布局,而不需要在路由更改时重新渲染。
  2. 不强制我在路由前添加任何前缀。
  3. 所有路由同时工作,使用户能够在不先注销的情况下返回到“/登录”或其他“auth”路由。
这种方案唯一的缺点是需要更多的代码和重复的路由,但这是我愿意承担的代价。

4
您可以尝试使用两个不同的switch语句来处理您的身份验证和受保护的路由。我在工作中遇到了类似的用例,使用两组switch代码块,并且每次只有一个运行是我选择的方式。
const App: React.FC = () => {
    const history = createBrowserHistory();

    return (
        <div className="App">
            <Router history={history}>
                {isLoggedIn ? <PrivateRoutes /> : <AuthRoutes />}
            </Router>
        </div>
    );
};


const PrivateRoutes: React.FC = () => {
    return (
        <>
            <Header />
            <Switch>
                <Route path="/home" component={HomePage} />
                <Route path="/dashboard" component={DashboardPage} />
                <Route path="*" component={NotFoundPage} />
            </Switch>
            <Footer />
        </>
    );
};

const AuthRoutes: React.FC = () => {
    return (
        <>
            <Header />
            <Switch>
                <Route path="/login" component={LoginPage} />
                <Route path="/sign-up" component={SignUpPage} />
                <Route path="*" component={NotFoundPage} />
            </Switch>
            <Footer />
        </>
    );
};

这是一个很好的解决方案!谢谢你!我最终使用了另一个解决方案,我会在我的答案中介绍,但我从你的解决方案中学到了很多! - ET-CS

-3

编辑: 我已经用另一种解决方案回答了这个问题。


虽然@Gaurab Kc和@johnny peter的两种解决方案都很好,但我最终做了类似于这样的事情:

<Router history={history}>
    <Switch>
        <PrivateRoute
            path="/home"
            component={HomePage}>
        </PrivateRoute>
        <PrivateRoute
            path="/dashboard"
            component={DashboardPage}>
        </PrivateRoute>
        <AuthRoute
            path="/login"
            component={LoginPage}>
        </AuthRoute>
        <AuthRoute
            path="/sign-up"
            component={SignUpPage}>
        </AuthRoute>
        <Route path="*" component={NotFoundPage}/>
    </Switch>
</Router>

AuthRoutePrivateRoute是类似于这样的:

interface PrivateRouteProps extends RouteProps {
    component: any;
}

const PrivateRoute = (props: PrivateRouteProps) => {
    const {component: Component, ...rest} = props;

    return (
        <Route
            {...rest}
            render={(routeProps) =>
                localStorage.getItem('user') ? (
                    <div>
                       ... // here is the app header
                       <Component {...routeProps} />
                       .. // here is the app footer
                    </div>
                ) : (
                    <Redirect
                        to={{
                            pathname: '/login',
                            state: {from: routeProps.location}
                        }}
                    />
                )
            }
        />
    );
};

export default PrivateRoute;

interface AuthRouteProps extends RouteProps {
    component: any;
}

const AuthRoute = (props: AuthRouteProps) => {
    const {component: Component, ...rest} = props;

    return (
        <Route
            {...rest}
            render={(routeProps) =>
                (
                    <div>
                       ... // here is the auth header
                       <Component {...routeProps} />
                       .. // here is the auth footer
                    </div>
                )
            }
        />
    );
};

export default AuthRoute;

3
这种方法完全错误,因为它违背了单页应用程序中布局的整体目的。您的布局包含特定屏幕常见的UI元素,当路由在布局内更改时不会重新渲染。但是,在您的方法中,当在相同的布局中切换不同的路由时,所有共同的UI也会被销毁并重新渲染,这违背了共同的页眉和页脚的整体目的。 - johnny peter
@johnnypeter 这是一个有效且重要的观点。但在这个阶段,我宁愿支付一些费用来获得一个不需要我设置路由为“/auth”和“/app”的解决方案。如果有其他解决方案,我会很高兴知道。谢谢。 - ET-CS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接