Material-UI 的选项卡与反应路由器 4 集成?
Posted
技术标签:
【中文标题】Material-UI 的选项卡与反应路由器 4 集成?【英文标题】:Material-UI's Tabs integration with react router 4? 【发布时间】:2017-05-29 01:35:17 【问题描述】:新的 react-router 语法使用 Link
组件来移动路由。但是这怎么能和material-ui
结合呢?
在我的例子中,我使用标签作为主要导航系统,所以理论上我应该有这样的东西:
const TabLink = ( onClick, href, isActive, label ) =>
<Tab
label=label
onActive=onClick
/>
export default class NavBar extends React.Component
render ()
return (
<Tabs>
<Link to="/">params => <TabLink label="Home" ...params/></Link>
<Link to="/shop">params => <TabLink label="shop" ...params/></Link>
<Link to="/gallery">params => <TabLink label="gallery" ...params/></Link>
</Tabs>
)
但是在渲染的时候,material-ui 会抛出一个错误,Tabs
的子元素必须是Tab
组件。有什么方法可以继续?如何管理标签的 isActive
属性?
提前致谢
【问题讨论】:
在这里完整回答 - ***.com/a/47396910/4180797 【参考方案1】:另一种解决方案 (https://codesandbox.io/s/l4yo482pll) 没有处理程序或 HOC,只有纯 react-router 和 material-ui 组件:
import React, Fragment from "react";
import ReactDOM from "react-dom";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Switch, Route, Link, BrowserRouter, Redirect from "react-router-dom";
function App()
const allTabs = ['/', '/tab2', '/tab3'];
return (
<BrowserRouter>
<div className="App">
<Route
path="/"
render=( location ) => (
<Fragment>
<Tabs value=location.pathname>
<Tab label="Item One" value="/" component=Link to=allTabs[0] />
<Tab label="Item Two" value="/tab2" component=Link to=allTabs[1] />
<Tab
value="/tab3"
label="Item Three"
component=Link
to=allTabs[2]
/>
</Tabs>
<Switch>
<Route path=allTabs[1] render=() => <div>Tab 2</div> />
<Route path=allTabs[2] render=() => <div>Tab 3</div> />
<Route path=allTabs[0] render=() => <div>Tab 1</div> />
</Switch>
</Fragment>
)
/>
</div>
</BrowserRouter>
);
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
【讨论】:
很好的解决方案! 这是最好的答案! 这是最好的解决方案! 简洁明了。我挣扎了一段时间,直到我找到了这个解决方案。我必须承认,我发现 Material UI 页面上的示例很难理解。我最初使用的是 Bootstrap,我很快就实现了这一目标。 代码沙箱已死js'【参考方案2】:我的导师帮助我使用 React Router 4.0 的 withRouter 来包装 Tabs 组件以启用这样的历史记录方法:
import React, Component from "react";
import Tabs, Tab from 'material-ui';
import withRouter from "react-router-dom";
import Home from "./Home";
import Portfolio from "./Portfolio";
class NavTabs extends Component
handleCallToRouter = (value) =>
this.props.history.push(value);
render ()
return (
<Tabs
value=this.props.history.location.pathname
onChange=this.handleCallToRouter
>
<Tab
label="Home"
value="/"
>
<div>
<Home />
</div>
</Tab>
<Tab
label="Portfolio"
value="/portfolio"
>
<div>
<Portfolio />
</div>
</Tab>
</Tabs>
)
export default withRouter(NavTabs)
只需将 BrowserRouter 添加到 index.js 即可。
【讨论】:
这很好,但由于value=this.props.history.location.pathname
,如果您将路由添加到特定元素或选项卡中未定义的任何路由,则会导致错误:Warning: Material-UI: the value provided `/portfolio/123` is invalid
。
我无法突出显示我的标签【参考方案3】:
您从 material-ui 看到的错误是因为它希望将 <Tab>
组件呈现为 <Tabs>
组件的直接子级。
现在,我发现了一种将链接集成到 <Tabs>
组件中而不会丢失样式的方法:
import React, Component from 'react';
import Tabs, Tab from 'material-ui/Tabs';
import Link from 'react-router-dom';
export default class MyComponent extends Component
render()
const location = this.props;
const pathname = location;
return (
<Tabs value=pathname>
<Tab label="First tab" containerElement=<Link to="/my-firs-tab-view" /> value="/my-firs-tab-view">
/* insert your component to be rendered inside the tab here */
</Tab>
<Tab label="Second tab" containerElement=<Link to="/my-second-tab-view" /> value="/my-second-tab-view">
/* insert your component to be rendered inside the tab here */
</Tab>
</Tabs>
);
要管理选项卡的“活动”属性,您可以使用<Tabs>
组件中的value
属性,并且您还需要为每个选项卡设置value
属性,因此当两个属性匹配时,它会将活动样式应用于该选项卡。
【讨论】:
文档中的containerElement
在哪里?它是如何使用的?【参考方案4】:
这是另一个解决方案,使用 Material 1.0 的测试版并添加浏览器 Back/Forward 组合:
import React from 'react';
import PropTypes from 'prop-types';
import withStyles from 'material-ui/styles';
import AppBar from 'material-ui/AppBar';
import Tabs, Tab from 'material-ui/Tabs';
import withRouter from "react-router-dom";
import Home from "./Home";
import Portfolio from "./Portfolio";
function TabContainer(props)
return <div style= padding: 20 >props.children</div>;
const styles = theme => (
root:
flexGrow: 1,
width: '100%',
marginTop: theme.spacing.unit * 3,
backgroundColor: theme.palette.background.paper,
,
);
class NavTabs extends React.Component
state =
value: "/",
;
componentDidMount()
window.onpopstate = ()=>
this.setState(
value: this.props.history.location.pathname
);
handleChange = (event, value) =>
this.setState( value );
this.props.history.push(value);
;
render()
const classes = this.props;
const value = this.state;
return (
<div className=classes.root>
<AppBar position="static" color="default">
<Tabs
value=value
onChange=this.handleChange
scrollable
scrollButtons="on"
indicatorColor="primary"
textColor="primary"
>
<Tab label="Home" value = "/" />
<Tab label="Portfolio" value = "/portfolio"/>
</Tabs>
</AppBar>
value === "/" && <TabContainer><Home /></TabContainer>
value === "/portfolio" && <TabContainer><Portfolio /></TabContainer>
</div>
);
NavTabs.propTypes =
classes: PropTypes.object.isRequired,
;
export default withRouter(withStyles(styles)(NavTabs));
【讨论】:
【参考方案5】:你可以使用 browserHistory
代替 React-Router Link
组件
import browserHistory from 'react-router'
// Go to /some/path.
onClick(label)
browserHistory.push('/$label');
// Example for Go back
//browserHistory.goBack()
<Tabs>
<Tab
label=label
onActive=() => onClick(label)
/>
</Tabs>
如您所见,您可以简单地将push()
您的目标发送到browserHistory
【讨论】:
v4 中没有browserHistory
,看看他们的index.js。
传递的道具props.push(url, state)
有类似的东西【参考方案6】:
正如@gkatchmar 所说,您可以使用withRouter
高阶组件,但您也可以使用context API
。由于@gkatchmar 已经显示了withRouter,我将只显示context API
。请记住,这是一个实验性 API。
https://***.com/a/42716055/3850405
import React, Component from "react";
import Tabs, Tab from 'material-ui';
import * as PropTypes from "prop-types";
export class NavTabs extends Component
constructor(props)
super(props);
static contextTypes =
router: PropTypes.object
handleChange = (event: any , value: any) =>
this.context.router.history.push(value);
;
render ()
return (
<Tabs
value=this.context.router.history.location.pathname
onChange=this.handleChange
>
<Tab
label="Home"
value="/"
>
<div>
<Home />
</div>
</Tab>
<Tab
label="Portfolio"
value="/portfolio"
>
<div>
<Portfolio />
</div>
</Tab>
</Tabs>
)
【讨论】:
【参考方案7】:带有 Tab 高亮显示的解决方案,基于 Typescript 并且与 react-route v5 配合使用:
解释:<Tab/>
这里作为 React 路由器的链接。 <Tab/> to='/all-event'
和 value='/all-event'
中的值应该相同才能突出显示
import Container, makeStyles, Tab, Tabs from '@material-ui/core';
import React from 'react';
import
Link,
Route,
Switch,
useLocation,
Redirect,
from 'react-router-dom';
import AllEvents from './components/AllEvents';
import UserEventsDataTable from './components/UserEventsDataTable';
const useStyles = makeStyles(() => (
container:
display: 'flex',
justifyContent: 'center',
,
));
function App()
const classes = useStyles();
const location = useLocation();
return (
<>
<Container className=classes.container>
<Tabs value=location.pathname>
<Tab
label='All Event'
component=Link
to=`/all-event`
value=`/all-event`
/>
<Tab
label='User Event'
component=Link
to=`/user-event`
value=`/user-event`
/>
</Tabs>
</Container>
<Switch>
<Route path=`/all-event`>
<AllEvents />
</Route>
<Route path=`/user-event`>
<UserEventsDataTable />
</Route>
<Route path=`/`>
<Redirect from='/' to='/all-event' />
</Route>
</Switch>
</>
);
export default App;
【讨论】:
【参考方案8】: <BrowserRouter>
<div className=classes.root>
<AppBar position="static" color="default">
<Tabs
value=this.state.value
onChange=this.handleChange
indicatorColor="primary"
textColor="primary"
fullWidth
>
<Tab label="Item One" component=Link to="/one" />
<Tab label="Item Two" component=Link to="/two" />
</Tabs>
</AppBar>
<Switch>
<Route path="/one" component=PageShell(ItemOne) />
<Route path="/two" component=PageShell(ItemTwo) />
</Switch>
</div>
【讨论】:
您能否添加几句话来解释您的代码以及它如何回答 OP 的问题?【参考方案9】:这是一个使用useLocation
挂钩的简单解决方案。不需要状态。不过 React 路由器 v5。
import Tab, Tabs from '@material-ui/core';
import matchPath, NavLink, useLocation from 'react-router-dom';
const navItems = [
id: 'one',
path: '/one',
text: 'One',
,
id: 'two',
path: '/two',
text: 'Two',
,
id: 'three',
path: '/three',
text: 'Three',
,
];
export default function Navigation()
const pathname = useLocation();
const activeItem = navItems.find((item) => !!matchPath(pathname, path: item.path ));
return (
<Tabs value=activeItem?.id>
navItems.map((item) => (
<Tab key=item.id value=item.id label=item.text component=NavLink to=item.path />
))
</Tabs>
);
【讨论】:
【参考方案10】:我创建了这个钩子来帮助控制选项卡并生成从位置 URL 捕获的默认值。
const useTabValue = (array, mainPath = "/") =>
const history = useHistory();
const pathname = useLocation();
const [value, setValue] = useState(0);
const pathArray = pathname.split("/");
function handleChange(_, nextEvent)
setValue(nextEvent);
history.push(`$mainPath/$array[nextEvent]`);
const findDefaultValue = useCallback(() =>
return array.forEach((el) =>
if (pathArray.indexOf(el) > 0)
setValue(array.indexOf(el));
return;
);
, [pathArray, array]);
useEffect(() =>
findDefaultValue();
, [findDefaultValue]);
return
handleChange,
value,
;
;
然后我就这样使用它:
const NavigationBar = () =>
const classes = useStyles();
const allTabs = useMemo(() => ["home", "search"]);
const handleChange, value = useTabValue(allTabs, "/dashboard");
return (
<div className=classes.navBarContainer>
<Tabs
centered
value=value
variant="fullWidth"
onChange=handleChange
className=classes.navBar
>
<Tab color="textPrimary" icon=<HomeIcon /> />
<Tab color="textPrimary" icon=<ExploreIcon /> />
</Tabs>
</div>
);
;
【讨论】:
【参考方案11】:我让它在我的应用程序中以这种方式工作:
import React, useEffect, useRef from 'react';
import PropTypes from 'prop-types';
import makeStyles from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';
import Typography from '@material-ui/core/Typography';
import Box from '@material-ui/core/Box';
import Container from "@material-ui/core/Container";
import Link from "react-router-dom";
import MenuIcon from "@material-ui/icons/Menu";
import VideoCallIcon from "@material-ui/icons/VideoCall";
const docStyles = makeStyles(theme => (
root:
display: 'flex',
'& > * + *':
marginLeft: theme.spacing(2),
,
,
appBarRoot:
flexGrow: 1,
,
headline:
marginTop: theme.spacing(2),
,
bodyCopy:
marginTop: theme.spacing(1),
fontSize: '1.2rem',
,
tabContents:
margin: theme.spacing(3),
,
));
function TabPanel(props)
const children, value, index, classes, ...other = props;
return (
<div
role="tabpanel"
hidden=value !== index
id=`simple-tabpanel-$index`
aria-labelledby=`simple-tab-$index`
...other
>
value === index && (
<Container>
<Box className=classes.tabContents>
children
</Box>
</Container>
)
</div>
);
function a11yProps(index)
return
id: `simple-tab-$index`,
'aria-controls': `simple-tabpanel-$index`,
;
function TabOneContents(props)
const classes = props;
return (
<>
<Typography variant="h4" component='h1' className=classes.headline>
Headline 1
</Typography>
<Typography variant="body1" className=classes.bodyCopy>
Body Copy 1
</Typography>
</>
)
function TabTwoContents(props)
const classes = props;
const nurseOnboardingPath = '/navigator/onboarding/' + Meteor.userId() + '/1';
return (
<>
<Typography variant="h4" component='h1' className=classes.headline>
Headline 2
</Typography>
<Typography variant="body1" className=classes.bodyCopy>
Body Copy 2
</Typography>
</>
)
export default function MUITabPlusReactRouterDemo(props)
const history, match = props;
const propsForDynamicClasses = ;
const classes = docStyles(propsForDynamicClasses);
const [value, setValue] = React.useState(history.location.pathname.includes('/tab_2') ? 1 : 0);
const handleChange = (event, newValue) =>
setValue(newValue);
const pathName = '/' + (value == 0 ? 'tab_1' : 'tab_2');
history.push(pathName);
;
return (
<div className=classes.appBarRoot>
<AppBar position="static" color="transparent">
<Tabs value=value onChange=handleChange aria-label="How It Works" textColor="primary">
<Tab label="Tab 1" ...a11yProps(0) />
<Tab label="Tab 2" ...a11yProps(1) />
</Tabs>
</AppBar>
<TabPanel value=value index=0 classes=classes>
<TabOneContents classes=classes/>
</TabPanel>
<TabPanel value=value index=1 classes=classes>
<TabTwoContents classes=classes/>
</TabPanel>
</div>
);
...在 React Router 中:
[.....]
<Route exact path="/tab_1"
render=(routeProps) =>
<MUITabPlusReactRouterDemo history=routeProps.history
/>
/>
<Route exact path="/tab_2"
render=(routeProps) =>
<MUITabPlusReactRouterDemo history=routeProps.history />
/>
[.....]
【讨论】:
【参考方案12】:import React, useContext, useEffect from "react";
import PropTypes from "prop-types";
import Drawer from "@material-ui/core/Drawer";
import IconButton from "@material-ui/core/IconButton";
import MenuIcon from "@material-ui/icons/Menu";
import Typography from "@material-ui/core/Typography";
import useStyles from "./Styles";
import Tabs from "@material-ui/core/Tabs";
import Tab from "@material-ui/core/Tab";
import Box from "@material-ui/core/Box";
import __t from "core/translation/translation";
import BrowserData from "core/helper/BrowserData";
import profileMenuItems from "./MenuItems";
import LayoutContext from "components/layout/core/LayoutContext";
import useHistory, useParams from "react-router-dom";
function TabPanel(props)
const children, value, index, ...other = props;
return (
<div
role="tabpanel"
hidden=value !== index
id=`vertical-tabpanel-$index`
aria-labelledby=`vertical-tab-$index`
...other
>
value === index && (
<Box p=3>
<Typography>children</Typography>
</Box>
)
</div>
);
TabPanel.propTypes =
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
;
export default function UserProfile(props)
const window = props;
const classes = useStyles();
const history = useHistory();
const page = useParams();
const isDesktop = useContext(LayoutContext);
const [open, setOpen] = React.useState(false);
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) =>
setValue(newValue);
history.push("/yourPath/" + newValue);
;
useEffect(() =>
if (!!page)
setValue(eval(page));
, [page]);
const getContent = () =>
const component: Component = MenuItems[value];
return <Component />;
;
const handleDrawerToggle = () =>
setOpen((prevState) => !prevState);
;
const Menu = (
<div>
<Tabs
orientation="vertical"
variant="scrollable"
value=value
onChange=handleChange
className=classes.tabs
>
MenuItems.map(
( label, iconPath, iconClassName = "" , index) => (
<Tab
label=label
id=`vertical-tab-$index`
aria-controls=`vertical-tabpanel-$index`
className=classes.tab
icon=
<img className=iconClassName src=iconPath alt=label />
/>
)
)
</Tabs>
</div>
);
return (
<div className=classes.root>
<IconButton
color="inherit"
aria-label="open drawer"
edge="start"
onClick=handleDrawerToggle
className=classes.drawerToggleButton
>
<MenuIcon />
</IconButton>
<nav className=classes.drawer>
<Drawer
anchor="left"
open=isDesktop ? true : open
onClose=handleDrawerToggle
variant=isDesktop ? "permanent" : "temporary"
classes=
paper: classes.drawerPaper,
ModalProps=
keepMounted: true,
>
Menu
</Drawer>
</nav>
<main className=classes.content>
<TabPanel
value=value
key=value
index=value
className=classes.tabPanel
>
getContent()
</TabPanel>
</main>
</div>
);
【讨论】:
以上是关于Material-UI 的选项卡与反应路由器 4 集成?的主要内容,如果未能解决你的问题,请参考以下文章
将 Twitter Bootstrap 导航选项卡与 Jade 一起使用