【问题标题】:How to detect matched route from a component outside of the <Route/> component that was matched using react-router?如何从使用 react-router 匹配的 <Route/> 组件之外的组件检测匹配的路由?
【发布时间】:2020-08-20 06:04:54
【问题描述】:

我在我的 React 应用程序中有以下结构,使用 react-router-dom

<Router>
  <Header/>
  <Main>
    <AllRoutes>      // THIS HANDLES THE SWITCH WITH ALL THE ROUTES
      <Switch>
        <Route exact path={ROUTES.HOME} component={Home}/>
        <Route exact path={ROUTES.ABOUT} component={About}/>
        <Route exact path={ROUTES.PRIVACY} component={Privacy}/>
        // ETC
      </Switch>
    </AllRoutes>
  </Main>
  <Footer/>         // <==== FOOTER NEEDS TO KNOW WHICH ROUTE HAS BEEN MATCH
<Router>

问题

页脚需要知道 &lt;Route/&gt; 匹配的内容。实现这一目标的最佳模式是什么?


选项 #1

我在react router docs 上找到了useRouteMatch 挂钩:

这有点用,但我认为这对我的情况来说还不够好。因为一个 URL 字符串可以匹配一个路由,但同时仍然不是一个有效的路由。

例如:

  • 路线:/:language/privacy
  • 有效路线:/en/privacy
  • 无效的路由也将匹配:/notALanguage/privacy

一旦一个路由匹配了,我通常需要在渲染组件页面或404页面之前检查它是否有效。

喜欢:

<Route exact path={"/:language/privacy"} render={(routeProps) => {
 const possibleLanguage = routeProps.match.params.language;
 if (possibleLanguage in LANGUAGES) {
   return(
     <PrivacyPage lang={possibleLanguage}/>
   );
 }
 else {
  return(
    <Page404/>
  );
 }
}}/>

选项 #2

我正在考虑做的是:

  • App.js 致电 useLocation。因此,当路线发生变化时,它总是会重新渲染。
  • 我可以在App.js 中添加一个detectRoute 函数来预先进行所有路线检查。
  • 我的AllRoutes 组件不需要组件。我会实现一个原生 JS 开关并渲染相应的路由。
  • 这样我就可以预先知道哪个&lt;Route/&gt; 将匹配,我可以将它传递给&lt;Footer/&gt; 或匹配的&lt;Route/&gt; 之外的任何组件。

类似这样的:

SandBox Link

export default function App() {
  console.log("Rendering App...");

  const location = useLocation();

  // THIS WOULD BE THE detectRoute FUNCTION
  // I COULD EVEN USE THE useRouteMatch HOOK IN HERE
  const matchedRoute =
    location.pathname === ROUTE1
      ? "ROUTE1"
      : location.pathname === ROUTE2
      ? "ROUTE2"
      : "404";

  return (
    <div>
      <div className="App">
        <Link to={ROUTE1}>Route 1</Link>
        <Link to={ROUTE2}>Route 2</Link>
        <Link to={"/whatever"}>Route 404</Link>
      </div>
      <div>
        <AllRoutes matchedRoute={matchedRoute} />
      </div>
    </div>
  );
}

function AllRoutes(props) {
  switch (props.matchedRoute) {
    case "ROUTE1":
      return <Route exact path={ROUTE1} component={Page1} />;
    case "ROUTE2":
      return <Route exact path={ROUTE2} component={Page2} />;
    default:
      return <Route exact path={"*"} component={Page404} />;
  }
}

它有效。但我想知道是否有适当的方法来做这件事,因为这看起来有点奇怪,而且可能有专门为此设计的东西。

【问题讨论】:

    标签: javascript reactjs react-router react-router-dom


    【解决方案1】:

    通常你想要:

    1. 将组件包装在一起
    2. 创建另一个开关来路由它们(并传递匹配参数)

    我整理了一个比较全面的example 选项。希望对您有所帮助!

    import React from "react";
    import "./styles.css";
    import { Switch, Link, Route, BrowserRouter } from "react-router-dom";
    
    const hoc = (Component, value) => () => (
      <>
        <main>
          <Component />
        </main>
        <Footer value={value} />
      </>
    );
    
    const Wrapper = ({ component: Component, value }) => (
      <>
        <main>
          <Component />
        </main>
        <Footer value={value} />
      </>
    );
    
    const WrapperRoute = ({ component, value, ...other }) => (
      <Route
        {...other}
        render={props => <Wrapper component={component} value={value} {...props} />}
      />
    );
    
    const Footer = ({ value }) => <footer>Footer! {value}</footer>;
    const Header = () => <header>Header!</header>;
    
    const Another = () => <Link to="/onemore">One More!</Link>;
    const Home = () => <Link to="/other">Other!</Link>;
    const OneMore = () => <Link to="/">Home!</Link>;
    const Other = () => <Link to="/another">Another!</Link>;
    
    export default () => (
      <BrowserRouter>
        <Header />
        <Switch>
          {/* You could inline it! */}
          <Route
            path="/another"
            render={() => (
              <>
                <main>
                  <Another />
                </main>
                <Footer value="" />
              </>
            )}
          />
          {/* You could use a custom route component (that uses an HOC or a wrapper) */}
          <WrapperRoute
            component={OneMore}
            path="/onemore"
            value="I got one more!"
          />
          {/* You could use a Higher-Order Component! */}
          <Route path="/other" component={hoc(Other, "I got other!")} />
          {/* You could use a wrapper component! */}
          <Route
            path="/"
            render={() => <Wrapper component={Home} value="I got home!" />}
          />
        </Switch>
        {/* You could have another switch for your footer (inline or within the component) */}
        <Switch>
          <Route
            path="/another"
            render={() => <Footer value="Switch footer another!" />}
          />
          <Route
            path="/other"
            render={() => <Footer value="Switch footer other!" />}
          />
          <Route
            path="/onemore"
            render={() => <Footer value="Switch footer onemore!" />}
          />
          <Route path="/" render={() => <Footer value="Switch footer home!" />} />
        </Switch>
      </BrowserRouter>
    );
    
    

    注意 WrapperRoute 允许您在传递匹配参数之前对其进行验证。如果需要,您可以发送Redirect

    【讨论】:

    • 谢谢,杰克。这些确实是很好的解决方案。我想我最终可能会将&lt;Footer/&gt; 移动到&lt;Routes/&gt; 内部。我将使用像&lt;Route .... render={(routeProps) =&gt; &lt;React.Fragment&gt;&lt;Componen1/&gt;&lt;Footer/&gt;&lt;/React.Fragment&gt;} /&gt;这样的渲染道具,我认为这是最简单的方法。你觉得呢?类似于您的Wrapper 解决方案,但使用来自&lt;Route/&gt; 的渲染道具。
    • 听起来您正在考虑内联示例。唯一的问题是,对于多个路由,您需要复制渲染方法的内容,或者有一个帮助函数来返回您需要的内容。您可能需要考虑使用 WrapperRoute 示例来集中您的逻辑。但是,任何示例都可以,做与您产生共鸣的事情!
    【解决方案2】:

    我最终做了什么:

    由于我使用的是 Redux,所以我添加了一个全局状态来跟踪匹配的路由。

    然后我从&lt;Route/&gt; 的组件的render 属性中调度操作以更新该状态。

    <Switch>
      <Route key={index} exact path={"/some-route"} render={(routeProps) => {
    
        // HERE I DISPATCH AN ACTION TO CHANGE THE STATE FOR THE CURRENT ROUTE
    
        dispatch({
          type: UPDATE_CURRENT_ROUTE,
          payload: { name: "SOME_ROUTE_NAME" }
        });
    
        return (
          <PrivacyPage
            {...routeProps}
          />
        );
    
      }}/>
    </Switch>
    

    现在我可以在 Footer.js 上做:

    function Footer() {
    
      const currentRoute = useSelector((state) => state.currentRoute);
    
      // RENDER FOOTER ACCORDINGLY TO THE CURRENT ROUTE
    }
    

    【讨论】:

      猜你喜欢
      • 2018-06-07
      • 2017-10-26
      • 1970-01-01
      • 1970-01-01
      • 2019-01-08
      • 2021-06-23
      • 2021-03-10
      • 1970-01-01
      • 2016-09-02
      相关资源
      最近更新 更多