根据路由处理条件组件渲染的正确方法?
Proper way of handling conditional component render depending on route?
我在 Web 和 SO 上搜索了很多,在 reactiflux chat 中询问,但没有找到干净且 non-hack-looking 渲染某些组件的方式 route/path.
假设我有 <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
是的,我可以把路由存入数组,写个循环,这样会更code-reusable。但这是处理此用例的最佳和最优雅的方式吗?
我相信它甚至可能有问题,例如,如果我不为具有路由 /xyz
的页面呈现 header 并且我有带有 UUID 的路由,例如 /projects/:id
,和 id=xyzfoo
,因此 /projects/xyzfoo
不会显示 header,但它应该显示。
将有关 header 的数据作为路由参数或查询包含在内。
/projects/:header布尔/:id
或者:
/projects/:id?header=false
然后您可以通过props.match或props.location访问它。
我认为你看错了。因此,在考虑 React 时,可组合性是首要特征。 header 是一个可重复使用的组件,可以放在任何你想要的地方!
这样思考会给你提供多种选择。
假设您有几个为应用程序设计的页面路由。页眉是任何使用它的页面的子组件!
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>
);
}
现在,在每个需要页眉的页面中,您可以在必要时引入页眉组件。
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>
);
}
An even more elegant but a bit more complex approach would be to
introduce a Higher order component. This would make your intentions more clear on adding headers at the route level!
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>
);
}
您可以先列出所有没有 header 的路由,然后将其他路由分组到其他开关中:
...
<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>
)
Routes without a path always match.
不幸的是,此解决方案可能对 "not found" 页面有问题。它应该放在 HeaderRoutes
的末尾,并将用 Header
.
呈现
Dhara's solution没有这样的问题。但如果 React Router 内部发生变化,它可能无法与 Switch
一起正常工作:
All children of a <Switch>
should be <Route>
or <Redirect>
elements.
Only the first child to match the current location will be rendered.
Route
上的 HOC 本身不是 Route
。但它应该可以正常工作,因为当前的代码库实际上期望任何 React.Element
具有与 <Route>
和 <Redirect>
具有相同的道具语义。
为了实现DRY规则(避免代码重复),根据路由实现条件渲染,你应该在以下结构上工作:
步骤 1) 创建布局 (HOC),其中 returns 使用 <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>
)
}
}
输出:
结论:
因此,无论何时您想要将页眉组件与路由定义的组件一起使用,请使用 <HeaderLayout />
,如果您不想使用页眉,则只需使用 <Route />
即可在您的页面中隐藏页眉.
您可以使用路由的渲染属性。
示例:
<Route path='/pageWithoutHeader' render={() => <Page1 />} />
<Route path='pageWithHeader' render={() =>
<Header/>
<Page2 />}
/>
这种方法比在页面内部使用页眉组件要好。
我经常想到的一个场景是数据库中的某些页面具有 Header = false 或 Page Title = false。一个好的解决方案是上下文。
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>
);
}
您也可以花点心思,将 useLocation 钩子与 useRef 结合使用来跟踪以前和当前的路径名,这样您就可以拥有默认状态,而无需在每个页面中声明 useHeader。
我在 Web 和 SO 上搜索了很多,在 reactiflux chat 中询问,但没有找到干净且 non-hack-looking 渲染某些组件的方式 route/path.
假设我有 <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
是的,我可以把路由存入数组,写个循环,这样会更code-reusable。但这是处理此用例的最佳和最优雅的方式吗?
我相信它甚至可能有问题,例如,如果我不为具有路由 /xyz
的页面呈现 header 并且我有带有 UUID 的路由,例如 /projects/:id
,和 id=xyzfoo
,因此 /projects/xyzfoo
不会显示 header,但它应该显示。
将有关 header 的数据作为路由参数或查询包含在内。
/projects/:header布尔/:id
或者:
/projects/:id?header=false
然后您可以通过props.match或props.location访问它。
我认为你看错了。因此,在考虑 React 时,可组合性是首要特征。 header 是一个可重复使用的组件,可以放在任何你想要的地方!
这样思考会给你提供多种选择。
假设您有几个为应用程序设计的页面路由。页眉是任何使用它的页面的子组件!
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>
);
}
现在,在每个需要页眉的页面中,您可以在必要时引入页眉组件。
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>
);
}
An even more elegant but a bit more complex approach would be to introduce a Higher order component. This would make your intentions more clear on adding headers at the route level!
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>
);
}
您可以先列出所有没有 header 的路由,然后将其他路由分组到其他开关中:
...
<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>
)
Routes without a path always match.
不幸的是,此解决方案可能对 "not found" 页面有问题。它应该放在 HeaderRoutes
的末尾,并将用 Header
.
Dhara's solution没有这样的问题。但如果 React Router 内部发生变化,它可能无法与 Switch
一起正常工作:
All children of a
<Switch>
should be<Route>
or<Redirect>
elements. Only the first child to match the current location will be rendered.
Route
上的 HOC 本身不是 Route
。但它应该可以正常工作,因为当前的代码库实际上期望任何 React.Element
具有与 <Route>
和 <Redirect>
具有相同的道具语义。
为了实现DRY规则(避免代码重复),根据路由实现条件渲染,你应该在以下结构上工作:
步骤 1) 创建布局 (HOC),其中 returns 使用 <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>
)
}
}
输出:
结论:
因此,无论何时您想要将页眉组件与路由定义的组件一起使用,请使用 <HeaderLayout />
,如果您不想使用页眉,则只需使用 <Route />
即可在您的页面中隐藏页眉.
您可以使用路由的渲染属性。 示例:
<Route path='/pageWithoutHeader' render={() => <Page1 />} />
<Route path='pageWithHeader' render={() =>
<Header/>
<Page2 />}
/>
这种方法比在页面内部使用页眉组件要好。
我经常想到的一个场景是数据库中的某些页面具有 Header = false 或 Page Title = false。一个好的解决方案是上下文。
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>
);
}
您也可以花点心思,将 useLocation 钩子与 useRef 结合使用来跟踪以前和当前的路径名,这样您就可以拥有默认状态,而无需在每个页面中声明 useHeader。