有没有办法用 React-Router v4+ 修改页面标题?
Posted
技术标签:
【中文标题】有没有办法用 React-Router v4+ 修改页面标题?【英文标题】:Is there a way to modify the page title with 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,没有多余的代码。
【参考方案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(() => 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 或一些编码资源上分享代码
注意:这适用于 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 答案***.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
如果您检查框架的<head>
,您可以看到更改:
【讨论】:
【参考方案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'>
【讨论】:
以上是关于有没有办法用 React-Router v4+ 修改页面标题?的主要内容,如果未能解决你的问题,请参考以下文章
react-router v4 - browserHistory 未定义
useNavigate 外部功能组件 - react-router v6
从 v3 迁移到 v4 react-router 时出现错误
在 react-router V4 中,如何以编程方式设置查询字符串参数?