【问题标题】:Proper way of handling conditional component render depending on route?根据路由处理条件组件渲染的正确方法?
【发布时间】:2019-03-15 04:48:48
【问题描述】:

我在 Web 和 SO 上进行了很多搜索,在 reactiflux 聊天中进行了询问,但没有找到一种干净且看起来不像 hack 的方式来根据路由/路径渲染某些组件。

假设我有<Header />,它应该显示在某些页面上,而应该隐藏在其他页面上。

当然我可以在 Header 组件中使用这个字符串

if (props.location.pathname.indexOf('/pageWithoutAHeader') > -1) return null

如果/pageWithoutAHeader 是唯一的,那很好。如果我需要 5 个页面的相同功能,它会变成这样:

if (props.location.pathname.indexOf('/pageWithoutAHeader1') > -1) return null
if (props.location.pathname.indexOf('/pageWithoutAHeader2') > -1) return null
if (props.location.pathname.indexOf('/pageWithoutAHeader3') > -1) return null

是的,我可以将路由存储在数组中并编写一个循环,这将更具代码可重用性。但这是处理此用例的最佳和最优雅的方式吗?

我相信它甚至可能是错误的,例如,如果我不为具有路由 /xyz 的页面呈现标题并且我有带有 UUID 的路由,例如 /projects/:idid=xyzfoo,所以 @987654328 @ 不会显示标题,但应该显示。

【问题讨论】:

    标签: reactjs react-router react-router-v4 react-router-dom react-router-v5


    【解决方案1】:

    为了实现DRY规则(避免代码重复)并根据路由实现条件渲染,您应该使用以下结构:

    第 1 步)创建布局(HOC),该布局使用<Header/> 返回给定组件并将其导出

    import React from "react"
    import { Route } from "react-router-dom"
    import Header from "./Header"
    
    export const HeaderLayout = ({component: Component, ...rest}) => (
        <Route {...rest} render={(props) => (
            <>
                <Header/>
                <Component {...props} />
            </>
        )} />
    )
    

    第二步)导入布局并使用

    import React, { Component } from 'react'
    import { BrowserRouter, Route, Switch } from "react-router-dom"
    import Test1 from './Test1';
    import Test2 from './Test2';
    import { HeaderLayout } from './HeaderLayout';
    
    export default class Main extends Component {
        render() {
            return (
                <BrowserRouter>
                    <Switch>
                        <HeaderLayout path="/test1" component={Test1} />
                        <Route path="/test2" component={Test2}/>
                    </Switch>
                </BrowserRouter>
    
            )
        }
    }
    

    输出:

    结论:

    因此,无论何时您想在路由定义的组件中包含标头组件,请使用 &lt;HeaderLayout /&gt;,如果您不想使用标头,则只需使用 &lt;Route /&gt; 在您的页面中隐藏标头。

    【讨论】:

      【解决方案2】:

      您可以先列出所有不带标题的路由,然后将其他路由分组在附加开关中:

      ...
      <Switch>
        <Route path="/noheader1" ... />
        <Route path="/noheader2" ... />
        <Route path="/noheader3" ... />
        <Route component={HeaderRoutes} />
      </Switch>
      ...
      
      HeaderRoutes = props => (
        <React.Fragment>
          <Header/>
          <Switch>
            <Route path="/withheader1" ... />
            <Route path="/withheader2" ... />
            <Route path="/withheader3" ... />
          </Switch>
        </React.Fragment>
      )
      

      来自documentation

      没有路径的路由总是匹配的。

      很遗憾,此解决方案可能存在“未找到”页面的问题。它应该放在HeaderRoutes 的末尾,并且会用Header 呈现。

      Dhara's solution 没有这个问题。但如果 R​​eact Router 内部发生变化,它可能不适用于 Switch

      &lt;Switch&gt; 的所有子元素都应该是 &lt;Route&gt;&lt;Redirect&gt; 元素。 只有第一个匹配当前位置的子节点才会被渲染。

      Route 上的 HOC 本身不是 Route。但它应该可以正常工作,因为当前的代码库实际上期望任何 React.Element&lt;Route&gt;&lt;Redirect&gt; 具有相同的 props 语义。

      【讨论】:

      • 哇,我必须检查一下,如果它像这样工作,那正是我需要的! :)
      • 我检查过了,它有效,谢谢!这是没有任何代码重复的最优雅的方式。我对它的工作原理感到惊讶,我会很感激解释我们如何使用Route 没有路径prop
      • 感谢您的解释!我也喜欢 Dhara 的解决方案(以及 jdc91 的解决方案,他首先使用 HOC),但我发现您的解决方案的代码重复性较低。
      • 你也可以使用 prop path="*" 虽然看起来有点多余。
      【解决方案3】:

      我认为你看错了。因此,在 React 中思考时,可组合性是第一大特征。标头是可重用的组件,可以随意放置!

      以这种方式思考会给你提供多种选择。

      假设您有几个为您的应用程序设计的页面路由。标题是使用它的任何页面的子组件!

      function AppRouter() {
        return (
          <Router>
            <div>
              <nav>
                <ul>
                  <li>
                    <Link to="/">Home</Link>
                  </li>
                  <li>
                    <Link to="/about/">About</Link>
                  </li>
                  <li>
                    <Link to="/users/">Users</Link>
                  </li>
                </ul>
              </nav>
      
              <Route path="/" exact component={Index} />
              <Route path="/about/" component={About} />
              <Route path="/users/" component={Users} />
            </div>
          </Router>
        );
      }
      

      现在,在每个页面中,您都可以使用页眉,只需在需要的地方引入页眉组件。

      export default function Index(){
          return (
              <React.Fragment>
                   <Header/>
                   <div> ... Index Content </div>
              </React.Fragment>
          );
      }
      
      export default function About(){
          return (
              <React.Fragment>
                   //I don't need a header here.
                   <div> ... Index Content </div>
              </React.Fragment>
          );
      }
      

      一种更优雅但更复杂的方法是 引入高阶组件。这将使您在路由级别添加标头的意图更加明确!

      function withHeader(Page){
          return class extends React.Component {
              render() {
                // Wraps the input component in a container, without mutating it.
                return (
                    <React.Fragment>
                       <Header/>
                       <Page {...this.props} />);
                    </React.Fragment>
              }
          }
      }
      
      function AppRouter() {
        return (
          <Router>
            <div>
              <nav>
                <ul>
                  <li>
                    <Link to="/">Home</Link>
                  </li>
                  <li>
                    <Link to="/about/">About</Link>
                  </li>
                  <li>
                    <Link to="/users/">Users</Link>
                  </li>
                </ul>
              </nav>
      
              <Route path="/" exact component={withHeader(Index)} />
              <Route path="/about/" component={About} />
              <Route path="/users/" component={Users} />
            </div>
          </Router>
         );
      }
      

      【讨论】:

      • 导入Header 并在任何地方使用它对我来说看起来像是一个大的代码重复,这并不能解决使代码的可重复性降低的问题。HOC 看起来更好一些,但它仍然会发生一些可重复的部分。有机会做同样的事情,但是用 something 包装多个 &lt;Route&gt; 元素,这样包装器内的每条路由都会用 Header 渲染组件?
      【解决方案4】:

      包含有关标头的这些数据作为路由参数或查询。

      /projects/:headerBool/:id

      或者:

      /projects/:id?header=false

      然后您可以通过 props.match 或 props.location 访问它。

      【讨论】:

      • 我不能说这是否是最佳解决方案,因为我不想在路由中存储标头“状态”+任何人都可以用他需要的东西替换它。
      • 真的!好吧,您可以通过用“/”分割路径名,然后为每个部分寻找完全匹配来降低它出错的可能性。如果你这样做的话,用你正在寻找的字符串和值为“真”的键来创建一个对象更有意义,这样你就可以在迭代它们时查找每个部分。
      【解决方案5】:

      您可以使用 Route 的 render 属性。 示例:

      <Route path='/pageWithoutHeader' render={() => <Page1 />} />
      <Route path='pageWithHeader' render={() => 
            <Header/>
            <Page2 />}
      />
      

      这种方法比在Page里面使用header组件要好。

      【讨论】:

        【解决方案6】:

        我经常弹出的一个场景是数据库中的某些页面具有 Header = false 或 Page Title = false。一个很好的解决方案是上下文。

        import React, { createContext, useContext, useState, useEffect } from "react";
        import { Switch, Route } from "react-router-dom";
        
        const AppContext = createContext({});
        
        const Header = () => {
          const { headerActive } = useContext(AppContext);
        
          if (!headerActive) {
            return null;
          }
        
          return (
            <header>I'm a header</header>
          );
        }
        
        const PageWithHeader = () => {
          const { setHeaderActive } = useContext(AppContext);
          useEffect(() => {
            setHeaderActive(true);
          },[setHeaderActive]);
        
          return (
            <div>Page with header</div>
          );
        }
        
        const PageWithoutHeader = () => {
          const { setHeaderActive } = useContext(AppContext);
          useEffect(() => {
            setHeaderActive(false);
          },[setHeaderActive]);
        
          return (
            <div>Page without header</div>
          );
        }
        
        export const App = () => {
          const [headerActive, setHeaderActive] = useState(true);
        
          return (
            <AppContext.Provider value={{ headerActive, setHeaderActive }}>
              <Header />
              <Switch>
                <Route path="page-1">
                  <PageWithHeader />
                </Route>
                <Route path="page-2">
                  <PageWithoutHeader />
                </Route>
              <Switch>
            </AppContext.Provider>
          );
        }
        

        您还可以使用自定义挂钩进一步简化它。比如:

        export const useHeader = (active) => {
          const { headerActive, setHeaderActive } = useContext(AppContext);
          useEffect(() => {
            if (active !== undefined) {
              setHeaderActive(active);
            }
          },[setHeaderActive, active]);
        
          return headerActive;
        }
        

        然后在原来的组件中:

        const Header = () => {
          const headerActive = useHeader();
        
          if (!headerActive) {
            return null;
          }
        
          return (
            <header>I'm a header</header>
          );
        }
        ...
        const PageWithoutHeader = () => {
          useHeader(false);
        
          return (
            <div>Page without header</div>
          );
        }
        

        您还可以使用 useLocation 挂钩与 useRef 配对来跟踪以前和当前的路径名,这样您就可以拥有默认状态,而无需在每个页面中声明 useHeader。

        【讨论】:

          猜你喜欢
          • 2020-11-06
          • 1970-01-01
          • 1970-01-01
          • 2018-11-26
          • 2021-08-08
          • 2022-01-27
          • 2021-10-27
          • 2019-12-17
          • 2020-05-21
          相关资源
          最近更新 更多