Есть ли способ изменить заголовок страницы с помощью React-Router v4 +?

Я ищу способ изменить заголовок страницы, когда React-Router v4 + меняет местоположение. Раньше я прислушивался к действию изменения местоположения в Redux и проверял этот маршрут на объект metaData.

При использовании React-Router v4 + нет списка фиксированных маршрутов. Фактически, различные компоненты сайта могут использовать Route с одной и той же строкой пути. Это означает, что старый метод, который я использовал, больше не будет работать.

Есть ли способ обновить заголовок страницы, вызывая действия при изменении определенных основных маршрутов, или есть лучший способ обновить метаданные сайта?

Я бы порекомендовал проверить react-helmet, это действительно упрощает такие вещи

Hunter McMillen 21.09.2018 18:31

вы используете подключенный-реагирующий-маршрутизатор?

Sagiv b.g 21.09.2018 19:17

@ Sagivb.g Да, я использую connected-react-router.

Kevin Ghadyani 21.09.2018 21:50

@Sawtaytoes, проверьте мой ответ ниже. Он использует реактивный маршрутизатор с одним компонентом-оболочкой без избыточного кода.

phen0menon 12.03.2019 13:16
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
64
4
52 648
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Ответ принят как подходящий

В вашем методе componentDidMount() сделайте это для каждой страницы

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

Это изменит заголовок вашей страницы, сделайте вышеупомянутое для каждого маршрута.

Также, если это больше, чем просто часть заголовка, проверьте реактивный шлем. Это очень удобная библиотека для этого, и она также обрабатывает некоторые приятные граничные случаи.

Я собираюсь использовать реактивный шлем, но подойдет и другое решение.

Kevin Ghadyani 25.09.2018 21:59

Это не отвечает на вопрос, даже использование шлема в componentDidMount () неэффективно. Есть ли способ сделать это через роутер, вот в чем вопрос.

TGarrett 21.11.2018 18:41

@TGarrett Это действительно ответ на вопрос, поэтому это принятый ответ. Что касается вашего запроса, вы можете использовать хук жизненного цикла реактивного маршрутизатора, чтобы сделать то же самое.

Adeel Imran 22.11.2018 05:50

Компоненты <Route /> имеют свойство оказывать. Таким образом, вы можете изменить заголовок страницы при изменении местоположения, объявив свои маршруты следующим образом:

<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

Обновление 1 августа 2019 г.. Это работает только с response-router> = 4.x. Благодаря @ su Supremebeing7

Обновленный ответ с использованием Реагировать на хуки:

Вы можете указать название любого маршрута с помощью компонента ниже, который построен с использованием useEffect.

import { useEffect } from "react";

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

export default Page;

А затем используйте Page в опоре render маршрута:

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

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

Это должен быть принятый ответ, он намного эффективнее и снижает потребность в стандартном коде.

Raptus 07.03.2019 14:44

@Raptus Вы можете найти более простое решение, но это все же полезно.

JulianSoto 20.04.2019 08:03

вы можете улучшить этот ответ с помощью примера с хуками: useEffect(() => { document.title = title; }, []) лично я использую кастомный хук, если заголовок зависит от props `import {isFunction} from 'lodash'; импортировать {useEffect} из "реагировать"; функция экспорта по умолчанию useTitle (titleOrFn, ... deps) {useEffect (() => {document.title = isFunction (titleOrFn)? titleOrFn (): titleOrFn;}, [... deps]); }; `тогда просто useTitle(()=> 'Profile of ' + userId, [userId])

TecHunter 17.05.2019 12:19

@TecHunter, пожалуйста, поделитесь кодом на jsfiddle или на каком-нибудь ресурсе кодирования

phen0menon 17.05.2019 14:38

Примечание: это для response-router> = 4.x. Пробовал на 3.x, и это не сработало, потому что у него нет поддержки render, поэтому мне пришлось настроить немного странный обходной путь / хак.

supremebeing7 27.06.2019 17:45
props.title должен быть в массиве зависимостей для эффекта, поскольку заголовок должен быть обновлен снова, если заголовок, переданный через реквизиты, изменится: `` `useEffect (() => {document.title = props.title ||" ";} , [props.title]); ``
mowwwalker 03.07.2020 18:57

Я обнаружил, что вы можете просто использовать title как <Route>, например: <Route path='/' title='Home' exact component = {Home} /> Затем вы можете получить доступ к заголовку через реквизиты и установить его. Это устраняет необходимость в оболочке <Page>.

RonE 13.10.2020 17:13

@RonE видеть различия между использованием component и render prop

phen0menon 26.02.2021 13:38

Исходя из отличного ответ phen0menon, почему бы не расширить Route вместо React.Component?

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

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

Page.propTypes = {
  title: PropTypes.string.isRequired,
};

export { Page };

Это приведет к удалению служебного кода, как показано ниже:

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

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

Обновлять:, другой способ сделать это - Хуки:

import { useEffect } from 'react';

/** Hook for changing title */
export const useTitle = title => {
  useEffect(() => {
    title && (document.title = title);
  }, [title]);
};

React рекомендует композицию вместо наследования, поэтому я бы не рекомендовал это. См .: reactjs.org/docs/composition-vs-inheritance.html

Jelle Blaauw 25.04.2019 10:22

Я предпочитаю этот ответ, но, к сожалению, это не "рекомендуемый способ"

TecHunter 17.05.2019 17:09

Изменено использование композиции, а также крючков. Удачного кодирования

Thierry Prost 06.08.2019 08:27

одна мелочь - вы имели ввиду Page вместо Route в блоке улучшение? наверное просто опечатка

Eliran Malka 03.09.2019 12:05

@jelle они рекомендуют против наследования, НО, насколько мне известно, это сделано для предотвращения склонности людей к использованию уже знакомых неоптимальных паттернов. Я не знаю о каком-либо фактическом риске или отрицательном результате использования этой стратегии в редких случаях. Это может быть исключительно полезно, но только в крайнем случае. Чтобы дать некоторый контекст, я сам использовал его в одном месте в проекте с тысячей файлов, чтобы подчеркнуть, как редко вам нужно это делать. Если есть какие-то реальные недостатки в использовании наследования, пожалуйста, поправьте меня.

Slbox 09.10.2019 01:57

Текущее решение не использует наследования.

Thierry Prost 09.10.2019 08:01

Я немного поработал над решением Thierry Prosts и в итоге получил следующее:

ОБНОВЛЕНИЕ Январь 2020: Теперь я обновил свой компонент, чтобы он тоже был в Typescript:

ОБНОВЛЕНИЕ Август 2021: Я добавил свой частный маршрут в 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;

Обновлено: Компонент My 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;

И моя реализация Router выглядела так:

// 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 04.08.2021 19:24

@Sel Я добавил свой компонент PrivateRoute в его текущий формат TypeScript. Это внизу моего поста. Я надеюсь, что это помогает.

Hsjakobsen 10.08.2021 10:31

С небольшой помощью 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>
  )
}

Вот мое решение, которое почти такое же, как простая установка 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 здесь, но не может обновить заголовок фрейма

Если вы осмотрите <head> рамы, вы увидите изменение: screenshot

Используя функциональный компонент на вашей главной странице маршрутизации, вы можете изменить заголовок при каждом изменении маршрута с помощью 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, я также вырезал косую черту и добавил значение по умолчанию, потому что домашний маршрут будет пустым.

Zimri Leijen 19.06.2020 18:30

Хорошее решение с меньшим количеством кода. Я использовал крючок useLocation и location.pathname вместо history.location.pathname. См. Ответ @Tolumide stackoverflow.com/a/64509041/3559967 ниже.

Antony 14.03.2021 07:07

@ Энтони Да, я согласен. Хук useLocation был бы лучше :)

Jordan Daniels 15.03.2021 00:06

Вы также можете использовать метод 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.info(document.title);
         return route.component;
       }}
     />
   );
 });
};

пример в коды (Открыть результат в новом окне, см. заголовок)

Пожалуйста, используйте реактивный шлем. Я хотел привести пример машинописного текста:

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

Я думаю, это был самый простой подход. Спасибо.

b_d_m_p 22.12.2020 08:48

Дэн Абрамов (создатель Redux и нынешний член команды React) создал компонент для установки заголовка, который также работает с новыми версиями React Router. Его очень легко использовать, и вы можете прочитать об этом здесь:

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

Например:

<DocumentTitle title='My Web App'>

Я отвечаю на это, потому что чувствую, что вы могли бы сделать дополнительный шаг, чтобы избежать повторений в ваших компонентах, и вы могли бы просто обновить заголовок из одного места (модуля маршрутизатора).

Обычно я объявляю свои маршруты как массив, но вы можете изменить свою реализацию в зависимости от вашего стиля. так что в основном что-то вроде этого ==>

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 = `Grade | 
        ${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('/', '')};}, [местоположение]); `

Antony 14.03.2021 10:36

Другие вопросы по теме