58

新的 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选项卡的道具?

提前致谢

4

13 回答 13

115

另一个没有处理程序或 HOC 的解决方案(https://codesandbox.io/s/l4yo482pll),只有纯 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);
于 2018-07-08T07:10:50.537 回答
35

我的导师帮助我使用 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 就可以了。

于 2017-09-01T20:01:49.180 回答
6

您从 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>
        );
    }
}

要管理选项卡的“活动”属性,您可以value在组件中使用该属性,<Tabs>并且您还需要value为每个选项卡设置一个属性,因此当两个属性匹配时,它会将活动样式应用于该选项卡。

于 2017-07-05T15:47:15.290 回答
2

这是另一种解决方案,使用 Material 1.0 的 beta 版并将浏览器 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));
于 2017-10-12T15:54:59.807 回答
2

带有选项卡突出显示的解决方案,基于 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;
于 2021-03-22T10:10:31.083 回答
1

You can use browserHistory instead of React-Router Link component

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>

As you see you can simply push() your target to the browserHistory

于 2017-01-14T20:33:03.687 回答
1

正如@gkatchmar 所说,您可以使用withRouter高阶组件,但也可以使用context API. 由于@gkatchmar 已经显示 withRouter 我只会显示context API. 请记住,这是一个实验性 API。

https://stackoverflow.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>           
    )
  }
}
于 2017-09-29T14:50:33.857 回答
0

我以一种更简单的方式解决了这个问题(我很惊讶它的效果如此之好——也许有一个我还没有发现的问题)。我正在使用路由器 6 和 React 17(我知道这些包更新)。无论如何,我只是在 handleChange 函数中使用了 useNavigate 钩子。因此,现在不需要 Switch,代码变得更加简单。见下文:

let navigate = useNavigate();
const [selection, setSelection] = useState();

const handleChange = (event, newValue) => {
    setSelection(newValue);
    navigate(`${newValue}`);
}

return (
    <Tabs value={selection} onChange={handleChange}>
         <Tab label="Products" value="products"  />
         <Tab label="Customers" value="customers" />
         <Tab label="Invoices" value="invoices" />
    </Tabs>
  );
}

handleChange 函数更新控制选项卡显示的“选择”,并导航到正确的路径。如果您在 React 空间中的某个位置设置组件,并正确设置 :style 路由(如 React Router 所述:https ://reactrouter.com/docs/en/v6/getting-started/overview ),您还可以控制内容将在页面的哪个区域呈现。希望它可以帮助某人!

于 2022-02-11T05:58:51.823 回答
0

useLocation这是一个使用钩子的简单解决方案。不需要状态。不过反应路由器 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>
  );
}
于 2021-02-19T18:41:26.637 回答
0
 <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>
于 2020-09-10T14:54:11.690 回答
0

我创建了这个钩子来帮助控制选项卡并生成从位置 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>
  );
};
于 2021-07-21T03:15:32.170 回答
-1

我让它在我的应用程序中以这种方式工作:

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 路由器中:

[.....]
<Route exact path="/tab_1"
       render={(routeProps) =>
           <MUITabPlusReactRouterDemo history={routeProps.history}
           />
       }/>

<Route exact path="/tab_2"
       render={(routeProps) =>
           <MUITabPlusReactRouterDemo history={routeProps.history}                           />
       }/>
[.....]
于 2020-07-23T20:08:04.007 回答
-1
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>
  );
}
于 2020-12-11T12:09:33.680 回答