【问题标题】:Material-ui adding Link component from react-routerMaterial-ui 从 react-router 添加 Link 组件
【发布时间】:2016-10-17 01:03:52
【问题描述】:

我正在努力将<Link/> 组件添加到我的material-ui AppBar

这是我的导航类:

class Navigation extends Component {
  constructor(props) {
    super(props)
  }

  render() {
    var styles = {
      appBar: {
        flexWrap: 'wrap'
      },
      tabs: {
        width: '100%'
      }
    }

    return (
      <AppBar showMenuIconButton={false} style={styles.appBar}>
        <Tabs style={styles.tabs}>
          <Tab label='Most popular ideas'/>
          <Tab label='Latest ideas' />
          <Tab label='My ideas' />
        </Tabs>
      </AppBar>
    )
  }
}

看起来不错:

标签是可点击的,有流畅的动画,这很酷。但是如何将它们与react-router 及其'&lt;Link/&gt; 组件连接在一起?

我试过像这样添加onChange 监听器:

<Tab
  label='My ideas'
  onChange={<Link to='/myPath'></Link>}
/>

但是我收到以下错误:

Uncaught Invariant Violation: Expected onChange listener to be a function, instead got type object

如果我尝试将 &lt;Tab/&gt; 组件包装到 &lt;Link/&gt; 组件中,我会收到 &lt;Tabs/&gt; 组件仅接受 &lt;Tab/&gt; 组件的错误。

这也不起作用(没有产生错误,但单击 Tab 不会将我带到路径):

<Tab label='Most popular ideas'>
  <Link to='/popular'/>
</Tab>

如何使&lt;Link/&gt; 组件与&lt;Tabs&gt;&lt;AppBar&gt; 一起工作?如果这不可能,我可以使用 material-ui 库中的任何其他组件来形成正确的菜单。

【问题讨论】:

    标签: javascript reactjs react-router material-ui


    【解决方案1】:

    这似乎对我有用

    import { Link as RouterLink } from 'react-router-dom';
    import Link from '@mui/material/Link';
    
    <Link to={menuItem.url} component={RouterLink} aria-current="page">
    {menuItem.label}
    </Link>
    

    【讨论】:

      【解决方案2】:

      使用href="" 选项,如下所示:

      <Tab
       href="/en/getting-started"
       label="Contact US"
       style={{ color: "white", textDecoration: "none" }}
      />
      

      要消除点击时的涟漪效应,请使用选项disableRipple

      【讨论】:

        【解决方案3】:

        如果你使用 NextJs,你可以这样做,并创建你自己的组件。

        *我没有用'a'标签包裹标签,因为它是自动添加的

        const WrapTab = (props) => {
        const { href } = props
        return (
        <Link href={href} style={{ width: "100%" }}>
          <Tab {...props} />
        </Link>
        )}
        

        这就是你的回报

            <Tabs
              value={value}
              indicatorColor="primary"
              textColor="primary"
              onChange={handleChange}
              variant="fullWidth"
            >
                <WrapTab href="/testPage/?tab=0" icon={<MenuIcon />} />
                <WrapTab href="/testPage/?tab=1" icon={<StampIcon2 />} />
                <WrapTab href="/testPage/?tab=2" icon={<ShopIcon />} />
                <WrapTab href="/testPage/?tab=3" icon={<PenIcon />} />
                <WrapTab href="/testPage/?tab=4" icon={<ProfileIcon />} />
            </Tabs>
        

        material-ui 文档的链接: https://material-ui.com/guides/composition/

        【讨论】:

          【解决方案4】:

          对于希望使用 Next.js 链接包装 Material-ui 链接的任何人

          import Link from "next/link"
          import MuiLink from "@material-ui/core/Link"
          
          const CustomNextLink = ({href, alt}) => ({children, ...rest}) => (
          <Link href={href} alt={alt}>
            <MuiLink {...rest}>
              {children}
            </MuiLink>
          </Link>)
          

          然后将它传递给你的 Tab 组件

          <Tab 
           key={...}
           label={title} 
           icon={icon} 
           component={CustomNextLink({href: to, alt: title})}
           style={...}
           className={...}
           classes={{selected: ...}}
           {...a11yProps(index)}
          />
          

          【讨论】:

            【解决方案5】:

            这是 React 的另一种实现,包括 hooks、Material-UI 和选项卡、React Router 和 Link 和 TypeScript。

            import * as React from "react";
            import { BrowserRouter as Router, Route, Redirect, Switch, Link, LinkProps } from 'react-router-dom';
            import AppBar from '@material-ui/core/AppBar';
            import Tabs from '@material-ui/core/Tabs';
            import { default as Tab, TabProps } from '@material-ui/core/Tab';
            
            import Home from './Home';
            import ProductManagement from './ProductManagement';
            import Development from './Development';
            import HomeIcon from '@material-ui/icons/Home';
            import CodeIcon from '@material-ui/icons/Code';
            import TimelineIcon from '@material-ui/icons/Timeline';
            
            const LinkTab: React.ComponentType<TabProps & LinkProps> = Tab as React.ComponentType<TabProps & LinkProps>;
            
            function NavBar() {
              const [value, setValue] = React.useState(0);
            
              const handleChange = (event: React.ChangeEvent<{}>, newValue: number) => {
                setValue(newValue);
              };
              return (
                <div >
                  <AppBar position="static" >
                    <Tabs value={value} onChange={handleChange} centered>
                      <LinkTab label='Home' icon={ <HomeIcon />} component={Link} to="/" />
                      <LinkTab label='Development' icon={<CodeIcon />} component={Link} to="/dev" />
                      <LinkTab label='Product Management' icon={<TimelineIcon />} component={Link} to="/pm" />
                    </Tabs>
                  </AppBar>
                </div>
              )
            };
            
            export default function App() {
              return (
                <Router>
                  <div>
                    <NavBar />
                    <Switch>
                      <Route exact path="/" component={ Home } />
                      <Route exact path="/dev" component={ Development } />
                      <Route exact path="/pm" component={ ProductManagement } />
                      <Redirect from="/" to="/" />
                    </Switch>
                  </div>
                </Router>
              )
            }
            

            【讨论】:

            • 看起来不错。有一个问题 - 如何设置选项卡的初始状态?您使用“0”,所以它是“/home”的路径,但是如果用户将以“/dev”路径开头怎么办?
            【解决方案6】:

            检查此链接,我实施了解决方案并为我工作

            Composition in material UI

            【讨论】:

              【解决方案7】:

              这可以使用 material-ui 中的 &lt;Link /&gt; 解决,而不是直接使用 react-router 中的 &lt;Link /&gt;&lt;NavLink /&gt;。可以在此处的文档中找到相同的示例。

              https://material-ui.com/components/links/

              另外&lt;Button /&gt;标签有一个组件prop来实现这个

              <Button color="inherit" component={Link} to={"/logout"}>Logout</Button>
              

              可以在这里找到关于此的广泛讨论

              https://github.com/mui-org/material-ui/issues/850

              【讨论】:

                【解决方案8】:

                由于我们使用的是 TypeScript,我无法使用 @hazardous 解决方案。这就是我们为material-ui v1.0.0-beta.16react-router 4.2.0 实现路由的方式。我们之所以拆分this.props.history.location.pathname,是因为例如我们需要访问/renewals/123。如果我们不这样做,我们会收到以下警告,并且不会显示任何选项卡处于活动状态:Warning: Material-UI: the value provided '/renewals/123' is invalid

                带有导入的完整代码:

                import * as React from "react";
                import * as ReactDOM from "react-dom";
                import * as ReactRouter from "react-router";
                import * as PropTypes from "prop-types";
                import { Switch, Route, Redirect, Link  } from "react-router-dom";
                import { Cases } from './../Cases';
                import { SidePane } from './../SidePane';
                import { withStyles, WithStyles } from 'material-ui/styles';
                import Paper from 'material-ui/Paper';
                import Tabs, { Tab } from 'material-ui/Tabs';
                import { withRouter } from "react-router-dom";
                import Badge from 'material-ui/Badge';
                import Grid from 'material-ui/Grid';
                import { Theme } from 'material-ui/styles';
                import SimpleLineIcons from '../../Shared/SimpleLineIcons'
                
                interface IState {
                    userName: string;
                }
                
                interface IProps {
                    history?: any
                }
                
                const styles = (theme: Theme) => ({
                    root: theme.typography.display1,
                    badge: {
                        right: '-28px',
                        color: theme.palette.common.white,
                    },
                    imageStyle:{
                        float: 'left',
                        height: '40px',
                        paddingTop: '10px'
                    },
                    myAccount: {
                        float: 'right'
                    },
                    topMenuAccount: {
                        marginLeft: '0.5em',
                        cursor: 'pointer'
                    }
                });
                
                type WithStyleProps = 'root' | 'badge' | 'imageStyle' | 'myAccount' | 'topMenuAccount';
                
                class Menu extends React.Component<IProps & WithStyles<WithStyleProps>, IState> {
                    constructor(props: IProps & WithStyles<WithStyleProps>) {
                        super(props);
                        this.state = {
                            userName: localStorage.userName ? 'userName ' + localStorage.userName : ""
                        }
                    }
                    componentDidMount() {
                        this.setState({ userName: localStorage.userName ? localStorage.userName : "" })
                    }
                    logout(event: any) {
                        localStorage.removeItem('token');
                        window.location.href = "/"
                    }
                
                    handleChange = (event: any, value: any) => {
                        this.props.history.push(value);
                    };
                
                    render() {
                        const classes = this.props.classes;
                        let route = '/' + this.props.history.location.pathname.split('/')[1];
                        return (
                            <div>
                                <Grid container spacing={24}>
                                    <Grid item xs={12} className={classes.root}>
                                        <img src="/Features/Client/Menu/logo.png" alt="Logo" className={classes.imageStyle} />
                                        <div className={this.props.classes.myAccount}>
                                        <span><span className={this.props.classes.topMenuAccount}>MY ACCOUNT</span><span className={classes.topMenuAccount}><SimpleLineIcons iconName={'user'} />&#x25BE;</span></span>
                                            <span onClick={this.logout} className={classes.topMenuAccount}><SimpleLineIcons iconName={'logout'} /></span>
                                        </div>
                                    </Grid>
                                    <Grid item xs={12} >
                                        <div className="route-list">
                                            <Tabs
                                                value={route}
                                                onChange={this.handleChange}
                                                indicatorColor="primary"
                                                textColor="primary"
                                            >
                                                <Tab label="Overview" value="/" />
                                                <Tab label={<Badge classes={{ badge: classes.badge }} badgeContent={this.props.caseRenewalCount} color="primary">
                                                    Renewals
                                                   </Badge>} value="/renewals" />
                                            </Tabs>
                                        </div>
                                    </Grid>
                                </Grid>
                            </div>
                        );
                    }
                }
                export default withStyles(styles)(withRouter(Menu))
                

                【讨论】:

                  【解决方案9】:

                  TypeScript 实现路由器驱动的选项卡。

                  对于那些寻找 TypeScript 实现的人。易于配置。由tabs 配置驱动。

                  interface ITabsPageProps {
                    match: match<{page: string}>;
                    history: History;
                  }
                  
                  const tabs = [{
                    label: 'Fist Tab',
                    link: 'fist-tab',
                    component: <FirstTabContent/>
                  }, {
                    label: 'Second Tab',
                    link: 'second-tab',
                    component: <SecondTabContent/>
                  }, {
                    label: 'Third Tab',
                    link: 'third-tab',
                    component: <ThirdTabContent/>
                  }];
                  
                  export class TabsPage extends React.Component<ITabsPageProps> {
                    handleChange(tabLink: string) {
                      this.props.history.push(`/tabs-page/${tabLink}`);
                    }
                  
                    render() {
                      const currentTab = this.props.match.params.page;
                      const selectedTab = tabs.find(tab => currentTab === tab.link);
                  
                      return (
                        <Fragment>
                          <Tabs
                            value={currentTab}
                            onChange={(event, value) => this.handleChange(value)}
                          >
                            {tabs.map(tab => (
                              <Tab
                                key={tab.link}
                                value={tab.link}
                                label={tab.label}
                              />
                            ))}
                          </Tabs>
                  
                          {selectedTab && selectedTab.component}
                        </Fragment>
                      );
                    }
                  }
                  

                  【讨论】:

                    【解决方案10】:

                    你可以试试这个简单的方法

                     <Tab label='Most popular ideas'  to='/myPath' component={Link} />
                    

                    【讨论】:

                    • 这对我有用。但是,无论如何要删除“蓝色下划线”链接样式?
                    • 这应该是最新材料-ui 4+的正确答案
                    • 2020 年仍然是一个更简单的解决方案
                    • 为我抛出 Error: React.Children.only expected to receive a single React element child.
                    • 没有抛出错误,但它对我不起作用。运行 4.11 核心用户界面
                    【解决方案11】:

                    对于带有 Typescript 的 Material UI 1.0:请参阅下面的 @ogglas 的 this post

                    对于带有纯 JS 的 Material-UI 1.0:

                    <Tabs value={value} onChange={this.handleChange}>
                      {
                        this.props.tabs.map(
                          ({label, path})=><Tab key={label} 
                                                label={label} 
                                                className={classes.tabLink} 
                                                component={Link} 
                                                to={path} />
                        )
                      }
                    </Tabs>
                    

                    classes.tabLink定义为:

                    tabLink : {
                        display:"flex",
                        alignItems:"center",
                        justifyContent:"center"
                    }
                    

                    这是如何工作的?

                    所有继承自ButtonBase 的mui 1.0 组件,都支持component 属性,参见ButtonBase。这个想法是允许您控制组件作为其包装器/根元素呈现的内容。 Tab 也有这个功能,虽然在写这个答案的时候这个道具没有明确记录,但是因为Tab 继承自ButtonBase,它的所有道具都继承了(并且文档确实覆盖这个)。

                    ButtonBase 的另一个特点是所有额外的道具,ButtonBase 或继承的组件没有使用,都分布在指定的component 上。我们已经使用这种行为来发送Link 使用的to 属性,方法是将其交给Tab 控制。您可以以相同的方式发送任何其他道具。请注意,ButtonBaseTab 都明确记录了这一点。

                    感谢 @josh-l 要求添加此内容。

                    【讨论】:

                    • 您能否更具体地了解为“组件”和“到”API 传递的参数是什么?我正在查看材料 UI v1 api 选项卡文档,但它没有显示其中任何一个? material-ui-1dab0.firebaseapp.com/api/tab
                    • 对于大多数 mui 容器组件,您可以发送一个 component 属性,使其使用不同的 React 组件而不是默认组件。在这个例子中,我们让 Tab 渲染反应路由器的 Link 控件。在这种模式下,mui 组件会将任何额外的道具传递给该组件。由于 Link 需要 to 道具,我在 Tab 中传递了它。你会发现这种行为记录在 mui 的某个地方。将尽快更新我的答案。
                    • 非常感谢!得到它的工作!但是,是的,也许用更多细节更新答案可以帮助其他人。无论哪种方式都赞成!
                    【解决方案12】:

                    您现在可以这样做:

                    <Tabs onChange={this.changeTab} value={value}>
                       <Tab value={0} label="first" containerElement={<Link to="/first"/>} />
                       <Tab value={1} label="second" containerElement={<Link to="/second"/>}/>
                       <Tab value={2} label="third" containerElement={<Link to="/third"/>} />
                     </Tabs>
                    

                    【讨论】:

                    • 甜蜜。但我注意到文档没有这样说。是否所有元素都支持containerElement
                    • 我尝试过的那些(他们拥有它是有道理的)他们确实拥有它
                    • 在撰写本文时,这种技术似乎不适用于material-ui@next
                    【解决方案13】:

                    所以我对这个解决方案的解决方案非常可靠,尽管它可能比您想要做的解决方案更手动。

                    我一直在使用的策略是实际上甚至不使用链接组件。相反,您将使用 Tabs onChange 属性作为回调,该回调可以响应 Tab 点击,并使用 Parent 上的 Props 手动跟踪位置。

                    您可以从 react-router 导入一个名为 History 的实用程序,该实用程序允许您手动推送位置。在使用 React-Router 时,您的组件树将可以访问 Location 属性,该属性的路径名键带有您当前位置的字符串。

                    我们将手动将此字符串解析为构成您当前 URL 的组件,然后使用 Switch 语句来决定当前选择了哪个选项卡以及单击选项卡时链接到的位置。 (这使您可以对导航进行相当程度的控制)

                    (例如 ['', 'latest'])

                    这是您的组件在集成此解决方案后的样子的模型。

                    import React from 'react';
                    import {History} from 'react-router';
                    
                    function parseLocation(location) {
                        if (String(location)) {
                            var locationArray = location.split('/');
                            return locationArray;
                        } else {
                            return false;
                        }
                    };
                    function filterPath(path) {
                        let locationArray = parseLocation(path);
                        return locationArray[locationArray.length - 1];
                    };
                    var Navigation = React.createClass({
                          mixins: [History],
                          getPage() {
                            if (this.props.location.pathname) {
                              let pathname = this.props.location.pathname;
                              let pageName = filterPath(pathname);
                              return pageName;
                            } else {
                              return false;
                            } 
                          },
                          decideContent() {
                            let page = this.getPage();
                            let content;
                            switch(page) {
                               case 'popular':
                                  content = 0;
                               case 'latest':
                                  content = 1;
                               case 'myideas':
                                  content = 2;
                               default:
                                  content = 0;
                            }
                            return content;
                          },
                          handleTabChange(value) {
                            let location = false;
                            switch (value) {
                               case 0:
                                 location = 'popular';
                                 break;
                               case 1:
                                 location = 'latest';
                                 break;
                               case 2:
                                 location = 'myideas';
                                 break;
                            }
                            if (location && location !== this.getPage()) {
                              this.history.pushState(null, '/'+location);
                            }
                          },
                          render() {
                             var styles = {
                              appBar: {
                               flexWrap: 'wrap'
                              },
                              tabs: {
                               width: '100%'
                              }
                             };
                             let content = this.decideContent();
                             let tabs = <Tabs
                                      onChange={this.handleTabChange}
                                      value={content}
                                    >
                                      <Tab label="Most Popular Ideas" value={0}  />
                                      <Tab label="Latest Ideas" value={1}  />
                                      <Tab label="My Ideas" value={2}  />
                                    </Tabs>;
                            return (
                             <AppBar showMenuIconButton={false} style={styles.appBar}>
                               {tabs}
                             </AppBar>
                            );
                          }
                    });
                    

                    【讨论】:

                    • 这对我来说似乎没问题。我会尝试将其添加到我的代码中。
                    猜你喜欢
                    • 2021-04-11
                    • 2018-11-14
                    • 2017-03-15
                    • 2021-02-21
                    • 2017-04-05
                    • 1970-01-01
                    • 2020-11-18
                    • 2020-11-22
                    • 2020-04-23
                    相关资源
                    最近更新 更多