根据路径处理条件组件渲染的正确方法?

时间:2019-03-15 04:48:48

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

我在Web和SO上进行了大量搜索,并在reactiflux聊天中询问,但没有找到一种干净且非骇客的方式来根据路线/路径呈现某些组件。< / p>

假设我有<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/:id和{{1 }},因此id=xyzfoo不会显示标题,但标题应该显示。

6 个答案:

答案 0 :(得分:5)

为了实现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} />
        </>
    )} />
)

第2步)导入布局并使用

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>

        )
    }
}

输出:

path-test1

path-test2

结论:

因此,每当您希望将标头组件与路由定义的组件一起包括时,请使用<HeaderLayout />,如果您不想使用标头,则只需使用<Route />即可在页面中隐藏标头。 / p>

答案 1 :(得分:4)

我认为您看错了这条路。因此,在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>
  );
}

现在在每个页面中,您都希望可以使用标题,并在需要时仅介绍Header Component。

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

答案 2 :(得分:2)

将此有关标头的数据作为路由参数或查询包括在内。

/ projects /:headerBool /:id

或者:

/ projects /:id?header = false

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

答案 3 :(得分:1)

您可以先列出所有没有标题的路由,然后在其他交换机中将其他路由分组:

...
<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没有这个问题。但是,如果React Router内部发生变化,它可能无法与Switch配合使用:

  

<Switch>的所有子代应为<Route><Redirect>元素。   将仅显示与当前位置匹配的第一个孩子。

Route上的

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

答案 4 :(得分:0)

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

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

此方法比使用Page内部的标头组件更好。

答案 5 :(得分:0)

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

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

您还可能会喜欢上使用与useRef配对的useLocation钩子来跟踪先前和当前的路径名,因此您可以具有默认状态而无需在每个页面中声明useHeader。