【问题标题】:Is there a way to modify the page title with React-Router v4+?有没有办法用 React-Router v4+ 修改页面标题?
【发布时间】:2019-02-26 02:32:11
【问题描述】:

我正在寻找一种在 React-Router v4+ 更改位置时修改页面标题的方法。我曾经在 Redux 中监听位置更改操作,并根据 metaData 对象检查该路由。

使用 React-Router v4+ 时,没有固定的路由列表。事实上,站点周围的各种组件都可以使用具有相同路径字符串的Route。这意味着我使用的旧方法将不再有效。

当某些主要路线发生变化时,我是否可以通过调用操作来更新页面标题,或者是否有更好的方法来更新站点的元数据?

【问题讨论】:

  • 我建议您查看react-helmet,它让这类事情变得非常容易
  • 你在使用connected-react-router吗?
  • @Sagivb.g 是的,我正在使用connected-react-router
  • @Sawtaytoes,请在下面查看我的答案。它使用带有一个包装组件的 react-router,没有多余的代码。

标签: reactjs react-router react-router-v4 page-title connected-react-router


【解决方案1】:

<Route /> 组件具有render 属性。因此,当位置更改时,您可以通过这样声明您的路线来修改页面标题:

<Route
  exact
  path="/"
  render={props => (
    <Page {...props} component={Index} title="Index Page" />
  )}
/>

<Route
  path="/about"
  render={props => (
    <Page {...props} component={About} title="About Page" />
  )}
/>

Page组件中可以设置路由标题:

import React from "react"

/* 
 * Component which serves the purpose of a "root route component". 
 */
class Page extends React.Component {
  /**
   * Here, we define a react lifecycle method that gets executed each time 
   * our component is mounted to the DOM, which is exactly what we want in this case
   */
  componentDidMount() {
    document.title = this.props.title
  }
  
  /**
   * Here, we use a component prop to render 
   * a component, as specified in route configuration
   */
  render() {
    const PageComponent = this.props.component

    return (
      <PageComponent />
    )
  }
}

export default Page

2019 年 8 月 1 日更新。这仅适用于 react-router >= 4.x。感谢@supremebeing7

使用React Hooks更新答案

您可以使用下面的组件指定任何路由的标题,该组件是使用useEffect 构建的。

import { useEffect } from "react";

const Page = (props) => {
  useEffect(() => {
    document.title = props.title || "";
  }, [props.title]);
  return props.children;
};

export default Page;

然后在路由的render 属性中使用Page

<Route
  path="/about"
  render={(props) => (
    <Page title="Index">
      <Index {...props} />
    </Page>
  )}
/>

<Route
  path="/profile"
  render={(props) => (
    <Page title="Profile">
      <Profile {...props} />
    </Page>
  )}
/>

【讨论】:

  • 这应该是公认的答案,这样更有效并且减少了对样板代码的需求。
  • @Raptus 你可能有一个更简单的解决方案,但这一个还很有用。
  • 你可以用一个带有钩子的例子来改进这个答案:useEffect(() =&gt; { document.title = title; }, []) 如果标题依赖于 props `import { isFunction } from 'lodash',我个人使用自定义钩子;从“反应”导入 { useEffect };导出默认函数 useTitle(titleOrFn, ...deps) { useEffect(() => { document.title = isFunction(titleOrFn) ? titleOrFn() : titleOrFn; }, [...deps]); }; ` 然后只需 useTitle(()=&gt; 'Profile of ' + userId, [userId])
  • @TecHunter 请在 jsfiddle 或一些编码资源上分享代码
  • 注意:这适用于 react-router >= 4.x。在 3.x 上尝试过,但没有成功,因为它没有 render 属性,所以我不得不设置一些奇怪的解决方法/hack。
【解决方案2】:

在您的 componentDidMount() 方法中为每个页面执行此操作

componentDidMount() {
  document.title = 'Your page title here';
}

这将更改您的页面标题,请为每条路线执行上述操作。

此外,如果它不仅仅是标题部分,请查看react-helmet 这是一个非常简洁的库,并且还可以处理一些不错的边缘情况。

【讨论】:

  • 我将使用 react-helmet,但其他解决方案也可以。
  • 这并不能回答问题,即使在 componentDidMount() 中使用头盔也没有效率。有没有办法通过路由器做到这一点,这是个问题。
  • @TGarrett 它确实回答了这个问题,这就是为什么它是公认的答案。关于您的查询,您可以使用 react-router 生命周期钩子来做同样的事情。
【解决方案3】:

借鉴优秀的answer of phen0menon,为什么不扩展Route而不是React.Component

import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
import PropTypes from 'prop-types';

export const Page = ({ title, ...rest }) => {
  useEffect(() => {
    document.title = title;
  }, [title]);
  return <Route {...rest} />;
};

这将删除开销代码,如下所示:

// old:
  <Route
    exact
    path="/"
    render={props => (
      <Page {...props} component={Index} title="Index Page" />
    )}
  />

// improvement:
  <Page
    exact
    path="/"
    component={Index}
    title="Index Page"
  />

更新:另一种方法是使用custom hook

import { useEffect } from 'react';

/** Hook for changing title */
export const useTitle = title => {
  useEffect(() => {
    const oldTitle = document.title;
    title && (document.title = title);
    // following line is optional, but will reset title when component unmounts
    return () => document.title = oldTitle;
  }, [title]);
};

【讨论】:

  • React 推荐组合而不是继承,所以我不推荐这个。见:reactjs.org/docs/composition-vs-inheritance.html
  • 我更喜欢这个答案,但很遗憾这不是“推荐的方式”
  • 更改为使用合成和钩子。快乐编码
  • 一件小事 - 您是指 improvement 块中的 Page 而不是 Route 吗?可能只是一个错字
  • @jelle 他们确实建议不要继承,BUT,据我所知,这是为了防止人们倾向于使用已经熟悉的次优模式.我不知道在极少数情况下使用此策略有任何实际风险或负面影响。它可能非常有用,但它应该是最后的手段。为了提供一些背景信息,我自己在千文件项目中的一个地方使用了它,以强调您很少需要接触它。如果使用继承有任何真正的缺点,请纠正我。
【解决方案4】:

使用主路由页面上的功能组件,您可以使用useEffect 在每次路由更改时更改标题。

例如,

const Routes = () => {
    useEffect(() => {
      let title = history.location.pathname
      document.title = title;
    });

    return (
      <Switch>
        <Route path='/a' />
        <Route path='/b' />
        <Route path='/c' />
      </Switch>
    );
}

【讨论】:

  • 这对我来说很有效,但是它是 window.location.pathname 我还切掉了斜线并添加了一个默认值,因为主路径是空白的。
  • 代码少的好解决方案。我使用了useLocation 钩子和location.pathname 而不是history.location.pathname。请参阅下面的@Tolumide 答案stackoverflow.com/a/64509041/3559967
  • @Antony 是的,我同意。 useLocation 挂钩会更好:)
【解决方案5】:

我在 Thierry Prosts 解决方案上做了一些构建,最终得到以下结果:

2020 年 1 月更新:我现在已将我的组件更新为也在 Typescript 中:

2021 年 8 月更新:我在 TypeScript 中添加了我的私人路线

import React, { FunctionComponent, useEffect } from 'react';
import { Route, RouteProps } from 'react-router-dom';

interface IPageProps extends RouteProps {
  title: string;
}

const Page: FunctionComponent<IPageProps> = props => {
  useEffect(() => {
    document.title = "Website name | " + props.title;
  });

  const { title, ...rest } = props;
  return <Route {...rest} />;
};

export default Page;

更新:我的 Page.jsx 组件现在是一个功能组件并带有 useEffect 挂钩:

import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';

const Page = (props) => {
  useEffect(() => {    
    document.title = "Website name | " + props.title;
  });

  const { title, ...rest } = props;
  return <Route {...rest} />;
}

export default Page;

您可以在下面找到我的初始解决方案:

// Page.jsx
import React from 'react';
import { Route } from 'react-router-dom';

class Page extends Route {
  componentDidMount() {
    document.title = "Website name | " + this.props.title;
  }

  componentDidUpdate() {      
      document.title = "Website name | " + this.props.title;
  }

  render() {
    const { title, ...rest } = this.props;
    return <Route {...rest} />;
  }
}

export default Page;

我的路由器实现如下所示:

// App.js / Index.js
<Router>
    <App>
      <Switch>
         <Page path="/" component={Index} title="Index" />
         <PrivateRoute path="/secure" component={SecurePage} title="Secure" />
      </Switch>
    </App>    
  </Router>

私人路线设置:

// PrivateRoute
function PrivateRoute({ component: Component, ...rest }) {
  return (
    <Page
      {...rest}
      render={props =>
        isAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect
            to={{
              pathname: "/",
              state: { from: props.location }
            }}
          />
        )
      }
    />
  );
}

TypeScript 中的私有路由:

export const PrivateRoute = ({ Component, ...rest }: IRouteProps): JSX.Element => {
  return (
    <Page
      {...rest}
      render={(props) =>
        userIsAuthenticated ? (
          <Component {...props} />
        ) : (
          <Redirect
            to={{
              pathname: Paths.login,
              state: { from: props.location },
            }}
          />
        )
      }
    />
  );
};

这使我能够使用新标题更新公共区域和更新私人区域。

【讨论】:

  • 这是一个很好的解决方案。你的 PrivateRoute 组件有 TypeScript 版本吗?
  • @Sel 我已经以当前的 TypeScript 格式添加了我的 PrivateRoute 组件。它在我的帖子底部。我希望它有所帮助。
【解决方案6】:

这是我的解决方案,它与简单地设置document.title 几乎相同,但使用useEffect

/**
* Update the document title with provided string
 * @param titleOrFn can be a String or a function.
 * @param deps? if provided, the title will be updated when one of these values changes
 */
function useTitle(titleOrFn, ...deps) {
  useEffect(
    () => {
      document.title = isFunction(titleOrFn) ? titleOrFn() : titleOrFn;
    },
    [...deps]
  );
}

这具有仅在您提供的deps 更改时才重新呈现的优势。 从不重新渲染:

const Home = () => {
  useTitle('Home');
  return (
    <div>
      <h1>Home</h1>
      <p>This is the Home Page</p> 
    </div>
  );
}

仅当我的userId 更改时才重新渲染:

const UserProfile = ({ match }) => {
  const userId = match.params.userId;
  useTitle(() => `Profile of ${userId}`, [userId]);
  return (
    <div>
      <h1>User page</h1>
      <p>
        This is the user page of user <span>{userId}</span>
      </p>
    </div>
  );
};

// ... in route definitions
<Route path="/user/:userId" component={UserProfile} />
// ...

CodePen here but cannot update frame title

如果您检查框架的&lt;head&gt;,您可以看到更改:

【讨论】:

    【解决方案7】:

    在 Helmet 的帮助下:

    import React from 'react'
    import Helmet from 'react-helmet'
    import { Route, BrowserRouter, Switch } from 'react-router-dom'
    
    function RouteWithTitle({ title, ...props }) {
      return (
        <>
          <Helmet>
            <title>{title}</title>
          </Helmet>
          <Route {...props} />
        </>
      )
    }
    
    export default function Routing() {
      return (
        <BrowserRouter>
          <Switch>
            <RouteWithTitle title="Hello world" exact={true} path="/" component={Home} />
          </Switch>
        </BrowserRouter>
      )
    }
    

    【讨论】:

      【解决方案8】:

      您也可以使用render 方法

      const routes = [
       {
         path: "/main",
         component: MainPage,
         title: "Main Page",
         exact: true
       },
       {
         path: "/about",
         component: AboutPage,
         title: "About Page"
       },
       {
         path: "/titlessPage",
         component: TitlessPage
       }
      ];
      
      const Routes = props => {
       return routes.map((route, idx) => {
         const { path, exact, component, title } = route;
         return (
           <Route
             path={path}
             exact={exact}
             render={() => {
               document.title = title ? title : "Unknown title";
               console.log(document.title);
               return route.component;
             }}
           />
         );
       });
      };
      

      codesandbox 的示例(在新窗口中打开结果以查看标题)

      【讨论】:

        【解决方案9】:

        我之所以回答这个问题,是因为我觉得您可以采取额外的步骤来避免组件中的重复,并且您可以从一个地方(路由器模块)更新标题。

        我通常将路由声明为数组,但您可以根据自己的风格更改实现。所以基本上是这样的 ==>

        import {useLocation} from "react-router-dom";
        const allRoutes = [
          {
                path: "/talkers",
                component: <Talkers />,
                type: "welcome",
                exact: true,
            },
            {
                path: "/signup",
                component: <SignupPage />,
                type: "onboarding",
                exact: true,
            },
          ]
        
        const appRouter = () => {
            const theLocation = useLocation();
            const currentLocation = theLocation.pathname.split("/")[1];
            React.useEffect(() => {
                document.title = `<Website Name> | 
                ${currentLocation[0].toUpperCase()}${currentLocation.slice(1,)}`
            }, [currentLocation])
        
           return (
             <Switch>
              {allRoutes.map((route, index) => 
                <Route key={route.key} path={route.path} exact={route.exact} />}
            </Switch>
        
           )
        
        }
        
        

        另一种方法是在每个allRoutes 对象中声明标题,并在此处使用@Denis Skiba 的解决方案。

        【讨论】:

        • 我没有做太多的路径名处理,所以useEffect 更简单:` useEffect(() => { document.title = Grade | ${location.pathname.replace('/', '')}; }, [location]); `
        【解决方案10】:

        请使用react-helmet。我想举个打字稿的例子:

        import { Helmet } from 'react-helmet';
        
        const Component1Title = 'All possible elements of the <head> can be changed using Helmet!';
        const Component1Description = 'No only title, description etc. too!';
        
        class Component1 extends React.Component<Component1Props, Component1State> {
          render () {
            return (
              <>
                <Helmet>
                  <title>{ Component1Title }</title>
                  <meta name="description" content={Component1Description} />
        
                </Helmet>
                ...
              </>
            )
          }
        }
        

        了解更多:https://github.com/nfl/react-helmet#readme

        【讨论】:

        • 这是我认为最简单的方法。谢谢。
        【解决方案11】:

        Dan Abramov(Redux 的创建者和 React 团队的现任成员)创建了一个用于设置标题的组件,该组件也适用于新版本的 React Router。 它非常易于使用,您可以在这里阅读:

        https://github.com/gaearon/react-document-title

        例如:

        <DocumentTitle title='My Web App'>
        

        【讨论】:

          猜你喜欢
          • 2020-12-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2022-01-14
          • 2020-01-10
          • 2018-12-29
          • 2017-10-22
          相关资源
          最近更新 更多