Я ищу способ изменить заголовок страницы, когда React-Router v4 + меняет местоположение. Раньше я прислушивался к действию изменения местоположения в Redux и проверял этот маршрут на объект metaData.
При использовании React-Router v4 + нет списка фиксированных маршрутов. Фактически, различные компоненты сайта могут использовать Route с одной и той же строкой пути. Это означает, что старый метод, который я использовал, больше не будет работать.
Есть ли способ обновить заголовок страницы, вызывая действия при изменении определенных основных маршрутов, или есть лучший способ обновить метаданные сайта?
вы используете подключенный-реагирующий-маршрутизатор?
@ Sagivb.g Да, я использую connected-react-router.
@Sawtaytoes, проверьте мой ответ ниже. Он использует реактивный маршрутизатор с одним компонентом-оболочкой без избыточного кода.





В вашем методе componentDidMount() сделайте это для каждой страницы
componentDidMount() {
document.title = 'Your page title here';
}
Это изменит заголовок вашей страницы, сделайте вышеупомянутое для каждого маршрута.
Также, если это больше, чем просто часть заголовка, проверьте реактивный шлем. Это очень удобная библиотека для этого, и она также обрабатывает некоторые приятные граничные случаи.
Я собираюсь использовать реактивный шлем, но подойдет и другое решение.
Это не отвечает на вопрос, даже использование шлема в componentDidMount () неэффективно. Есть ли способ сделать это через роутер, вот в чем вопрос.
@TGarrett Это действительно ответ на вопрос, поэтому это принятый ответ. Что касается вашего запроса, вы можете использовать хук жизненного цикла реактивного маршрутизатора, чтобы сделать то же самое.
Компоненты <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 Вы можете найти более простое решение, но это все же полезно.
вы можете улучшить этот ответ с помощью примера с хуками: 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, пожалуйста, поделитесь кодом на jsfiddle или на каком-нибудь ресурсе кодирования
Примечание: это для response-router> = 4.x. Пробовал на 3.x, и это не сработало, потому что у него нет поддержки render, поэтому мне пришлось настроить немного странный обходной путь / хак.
props.title должен быть в массиве зависимостей для эффекта, поскольку заголовок должен быть обновлен снова, если заголовок, переданный через реквизиты, изменится: `` `useEffect (() => {document.title = props.title ||" ";} , [props.title]); ``
Я обнаружил, что вы можете просто использовать title как <Route>, например: <Route path='/' title='Home' exact component = {Home} /> Затем вы можете получить доступ к заголовку через реквизиты и установить его. Это устраняет необходимость в оболочке <Page>.
@RonE видеть различия между использованием component и render prop
Исходя из отличного ответ 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
Я предпочитаю этот ответ, но, к сожалению, это не "рекомендуемый способ"
Изменено использование композиции, а также крючков. Удачного кодирования
одна мелочь - вы имели ввиду Page вместо Route в блоке улучшение? наверное просто опечатка
@jelle они рекомендуют против наследования, НО, насколько мне известно, это сделано для предотвращения склонности людей к использованию уже знакомых неоптимальных паттернов. Я не знаю о каком-либо фактическом риске или отрицательном результате использования этой стратегии в редких случаях. Это может быть исключительно полезно, но только в крайнем случае. Чтобы дать некоторый контекст, я сам использовал его в одном месте в проекте с тысячей файлов, чтобы подчеркнуть, как редко вам нужно это делать. Если есть какие-то реальные недостатки в использовании наследования, пожалуйста, поправьте меня.
Текущее решение не использует наследования.
Я немного поработал над решением 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 Я добавил свой компонент PrivateRoute в его текущий формат TypeScript. Это внизу моего поста. Я надеюсь, что это помогает.
С небольшой помощью 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} />
// ...
Используя функциональный компонент на вашей главной странице маршрутизации, вы можете изменить заголовок при каждом изменении маршрута с помощью 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 ниже.
@ Энтони Да, я согласен. Хук useLocation был бы лучше :)
Вы также можете использовать метод 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
Я думаю, это был самый простой подход. Спасибо.
Дэн Абрамов (создатель 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('/', '')};}, [местоположение]); `
Я бы порекомендовал проверить
react-helmet, это действительно упрощает такие вещи